From b5de192f72a270419d2f154496c97c5c2725e0ac Mon Sep 17 00:00:00 2001 From: Sergio Moreno Date: Tue, 31 Mar 2026 17:08:02 +0200 Subject: [PATCH] feat!: modernize to v6.0.0 with context support and Go 1.26 BREAKING CHANGES: - Require Go 1.26+ (previously Go 1.11+) - Add context.Context as first parameter to all HTTP methods: Trigger, TriggerWithParams, TriggerMulti, TriggerMultiWithParams, TriggerBatch, SendToUser, Channels, Channel, GetChannelUsers - Remove deprecated methods: TriggerExclusive, TriggerMultiExclusive, AuthenticatePrivateChannel, AuthenticatePresenceChannel - Remove deprecated EncryptionMasterKey field Changes: - Replace io/ioutil with io package (deprecated since Go 1.16) - Replace panics with error returns in crypto.go - Add missing error check after http.NewRequest - Fix typo: Paramater -> parameter in error messages - Simplify boolean returns in util.go - Update golang.org/x/crypto to v0.49.0 - Update testify to v1.11.1 (github.com/stretchr/testify) - Update GitHub Actions to use Go 1.25/1.26 and latest action versions --- .github/workflows/test.yml | 10 +- CHANGELOG.md | 40 +++- MODERNIZATION.md | 331 +++++++++++++++++++++++++++++++++ README.md | 61 ++++-- channel_authentication_test.go | 10 +- client.go | 159 +++++----------- client_test.go | 197 +++++++------------- crypto.go | 31 +-- crypto_test.go | 8 +- encoder.go | 15 +- go.mod | 15 +- go.sum | 24 +-- request.go | 19 +- request_url_test.go | 2 +- response_parsing_test.go | 2 +- util.go | 12 +- util_test.go | 8 +- webhook_test.go | 2 +- 18 files changed, 606 insertions(+), 340 deletions(-) create mode 100644 MODERNIZATION.md diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 500d034..8f9eb06 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,20 +7,20 @@ on: jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: fail-fast: false matrix: - go: ['1.18', '1.19'] + go: ['1.25', '1.26'] name: Go ${{ matrix.go }} Test steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} @@ -37,7 +37,7 @@ jobs: finish: needs: test - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: shogo82148/actions-goveralls@v1 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index a62cb32..313687f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,41 @@ -# Changelog - +# Changelog + +## 6.0.0 + +### ⚠️ Breaking Changes + +#### Minimum Go Version +- **BREAKING**: Now requires Go 1.26 or later (previously Go 1.11+) + +#### Context Support +- **BREAKING**: All HTTP methods now require `context.Context` as the first parameter: + - `Trigger(ctx, channel, event, data)` + - `TriggerWithParams(ctx, channel, event, data, params)` + - `TriggerMulti(ctx, channels, event, data)` + - `TriggerMultiWithParams(ctx, channels, event, data, params)` + - `TriggerBatch(ctx, batch)` + - `SendToUser(ctx, userId, event, data)` + - `Channels(ctx, params)` + - `Channel(ctx, name, params)` + - `GetChannelUsers(ctx, name)` + +#### Removed Deprecated APIs +- **BREAKING**: Removed `TriggerExclusive()` - use `TriggerWithParams()` with `SocketID` parameter +- **BREAKING**: Removed `TriggerMultiExclusive()` - use `TriggerMultiWithParams()` with `SocketID` parameter +- **BREAKING**: Removed `AuthenticatePrivateChannel()` - use `AuthorizePrivateChannel()` +- **BREAKING**: Removed `AuthenticatePresenceChannel()` - use `AuthorizePresenceChannel()` +- **BREAKING**: Removed `EncryptionMasterKey` field - use `EncryptionMasterKeyBase64` + +### Changed +- Replaced deprecated `io/ioutil` with `io` package +- Updated `golang.org/x/crypto` to v0.49.0 +- Updated `testify` to v1.11.1 with new import path `github.com/stretchr/testify` +- Improved error handling (no more panics in library code) +- Fixed typo: "Paramater" → "parameter" in error messages + +### Fixed +- Added missing error check after `http.NewRequest` in request handling + ## 5.1.1 - [CHANGED] readme example for user authentication diff --git a/MODERNIZATION.md b/MODERNIZATION.md new file mode 100644 index 0000000..2de88b1 --- /dev/null +++ b/MODERNIZATION.md @@ -0,0 +1,331 @@ +# pusher-http-go v6 Modernization Plan + +## Executive Summary + +This document outlines the plan to modernize `pusher-http-go` to v6.0.0, targeting **Go 1.26+** with breaking changes to remove deprecated features, add context support, and eliminate code smells. + +**Current State:** +- Go version: 1.14 +- Last significant update: 2021 (v5.1.0) +- Contains deprecated `io/ioutil` usage +- Missing `context.Context` support +- Contains deprecated methods scheduled for removal + +--- + +## 1. High Priority: Breaking Changes (v6.0.0) + +### 1.1 Update Go Version + +| File | Change | +|------|--------| +| `go.mod:3` | `go 1.14` → `go 1.26` | +| `.github/workflows/test.yml:14` | Test matrix `['1.18', '1.19']` → `['1.25', '1.26']` | +| `README.md:13` | Update "Supports Go 1.11 or greater" → "Supports Go 1.26 or greater" | + +### 1.2 Add Context Support + +Add `context.Context` parameter to all HTTP methods for proper cancellation/timeout support: + +| File | Method | New Signature | +|------|--------|---------------| +| `client.go` | `Trigger` | `Trigger(ctx context.Context, channel, eventName string, data interface{}) error` | +| `client.go` | `TriggerWithParams` | Add ctx first param | +| `client.go` | `TriggerMulti` | Add ctx first param | +| `client.go` | `TriggerMultiWithParams` | Add ctx first param | +| `client.go` | `TriggerBatch` | Add ctx first param | +| `client.go` | `SendToUser` | Add ctx first param | +| `client.go` | `Channels` | Add ctx first param | +| `client.go` | `Channel` | Add ctx first param | +| `client.go` | `GetChannelUsers` | Add ctx first param | +| `request.go:24` | `http.NewRequest` → `http.NewRequestWithContext` | + +**Example migration:** + +```go +// Before (v5) +err := client.Trigger("my-channel", "my-event", data) + +// After (v6) +err := client.Trigger(context.Background(), "my-channel", "my-event", data) + +// With timeout +ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) +defer cancel() +err := client.Trigger(ctx, "my-channel", "my-event", data) +``` + +### 1.3 Remove Deprecated Methods + +| File | Method/Field | Status | Replacement | +|------|--------------|--------|-------------| +| `client.go:233-237` | `TriggerExclusive()` | **Remove** | `TriggerWithParams()` with `SocketID` | +| `client.go:248-252` | `TriggerMultiExclusive()` | **Remove** | `TriggerMultiWithParams()` with `SocketID` | +| `client.go:584-586` | `AuthenticatePrivateChannel()` | **Remove** | `AuthorizePrivateChannel()` | +| `client.go:624-626` | `AuthenticatePresenceChannel()` | **Remove** | `AuthorizePresenceChannel()` | +| `client.go:58` | `EncryptionMasterKey string` | **Remove** | `EncryptionMasterKeyBase64` | +| `client.go:730-742` | Logic for deprecated `EncryptionMasterKey` | **Remove** | N/A | + +--- + +## 2. Medium Priority: Deprecated Package Updates + +### 2.1 Replace `io/ioutil` (deprecated since Go 1.16) + +| File | Line | Current | Replacement | +|------|------|---------|-------------| +| `request.go:7` | `import "io/ioutil"` | `import "io"` | +| `request.go:39` | `ioutil.ReadAll(response.Body)` | `io.ReadAll(response.Body)` | +| `client_test.go:6` | `import "io/ioutil"` | `import "io"` | +| `client_test.go:363,390,428,469,490` | `ioutil.ReadAll(req.Body)` | `io.ReadAll(req.Body)` | + +### 2.2 Update Documentation Examples + +| File | Lines | Update | +|------|-------|--------| +| `client.go:501,559,596,690` | Doc examples with `ioutil.ReadAll` | Use `io.ReadAll` | +| `README.md:420,463,536,738` | Examples with `ioutil.ReadAll` | Use `io.ReadAll` | + +--- + +## 3. Medium Priority: Code Smells & Bug Fixes + +### 3.1 Missing Error Check (Bug) + +**File:** `request.go:24-28` + +**Issue:** `http.NewRequest` error is not checked before using `req`. + +**Current:** +```go +req, err := http.NewRequest(method, url, bytes.NewBuffer(body)) +for key, val := range headers { + req.Header.Set(http.CanonicalHeaderKey(key), val) +} +``` + +**Fixed:** +```go +req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(body)) +if err != nil { + return nil, err +} +for key, val := range headers { + req.Header.Set(http.CanonicalHeaderKey(key), val) +} +``` + +### 3.2 Replace Panics with Errors + +Library code should never panic. These should return errors instead. + +| File | Line | Function | Current | Fixed | +|------|------|----------|---------|-------| +| `crypto.go:67-78` | `formatMessage()` | `panic(err)` | Return `(string, error)` | +| `crypto.go:80-87` | `generateNonce()` | `panic(err)` | Return `([24]byte, error)` | + +**Current:** +```go +func formatMessage(nonce string, cipherText string) string { + // ... + json, err := json.Marshal(encryptedMessage) + if err != nil { + panic(err) + } + return string(json) +} +``` + +**Fixed:** +```go +func formatMessage(nonce string, cipherText string) (string, error) { + // ... + json, err := json.Marshal(encryptedMessage) + if err != nil { + return "", fmt.Errorf("failed to marshal encrypted message: %w", err) + } + return string(json), nil +} +``` + +### 3.3 Replace `errors.New(fmt.Sprintf(...))` with `fmt.Errorf` + +| File | Line | Current | Fixed | +|------|------|---------|-------| +| `encoder.go:50` | `errors.New(fmt.Sprintf("Event payload exceeded maximum size (%d bytes is too much)", len(payloadData)))` | `fmt.Errorf("event payload exceeded maximum size (%d bytes is too much)", len(payloadData))` | +| `encoder.go:59` | `errors.New(fmt.Sprintf("Paramater %s specified multiple times", k))` | `fmt.Errorf("parameter %s specified multiple times", k)` | + +### 3.4 Fix Typo + +| File | Line | Current | Fixed | +|------|------|---------|-------| +| `encoder.go:59` | `"Paramater"` | `"Parameter"` | + +### 3.5 Simplify Boolean Returns + +| File | Function | Current | Simplified | +|------|----------|---------|------------| +| `util.go:61-66` | `validChannel()` | `if ... { return false } return true` | `return len(channel) <= maxChannelNameSize && channelValidationRegex.MatchString(channel)` | +| `util.go:77-82` | `isEncryptedChannel()` | `if ... { return true } return false` | `return strings.HasPrefix(channel, "private-encrypted-")` | + +--- + +## 4. Low Priority: Dependency Updates + +### 4.1 Update Dependencies + +| Dependency | Current | Update To | +|------------|---------|-----------| +| `golang.org/x/crypto` | `v0.0.0-20200709230013-948cd5f35899` | `v0.49.0` | +| `gopkg.in/stretchr/testify.v1` | `v1.2.2` | `github.com/stretchr/testify v1.11.1` | + +**Note:** The testify import path changed from `gopkg.in/stretchr/testify.v1` to `github.com/stretchr/testify`. All test files need updating. + +### 4.2 Update GitHub Actions + +| File | Item | Current | Update To | +|------|------|---------|-----------| +| `.github/workflows/test.yml:10` | Runner | `ubuntu-20.04` | `ubuntu-latest` | +| `.github/workflows/test.yml:21` | Checkout | `actions/checkout@v2` | `actions/checkout@v4` | +| `.github/workflows/test.yml:24` | Setup Go | `actions/setup-go@v2` | `actions/setup-go@v5` | +| `.github/workflows/test.yml:40` | Runner (finish job) | `ubuntu-20.04` | `ubuntu-latest` | + +--- + +## 5. File-by-File Change Summary + +| File | Changes Required | +|------|------------------| +| `go.mod` | Go version 1.22, update dependencies | +| `client.go` | Add context params, remove deprecated methods/fields, update doc examples | +| `request.go` | Add context support, replace ioutil→io, add error check | +| `crypto.go` | Replace panic with error returns | +| `encoder.go` | Use fmt.Errorf, fix typo | +| `util.go` | Simplify boolean return statements | +| `client_test.go` | Replace ioutil→io, update tests for context params | +| `channel_authentication_test.go` | Update testify import | +| `crypto_test.go` | Update testify import | +| `request_url_test.go` | Update testify import | +| `response_parsing_test.go` | Update testify import | +| `util_test.go` | Update testify import | +| `webhook_test.go` | Update testify import | +| `README.md` | Update Go version requirement, update examples | +| `CHANGELOG.md` | Add v6.0.0 migration guide | +| `.github/workflows/test.yml` | Update Go versions, action versions | + +--- + +## 6. Implementation Order + +### Phase 1: Dependencies & Infrastructure +- [x] Update `go.mod` to Go 1.26 +- [x] Update `golang.org/x/crypto` to v0.49.0 +- [x] Update testify to `github.com/stretchr/testify v1.11.1` +- [x] Update all test file imports for testify +- [x] Update GitHub Actions workflow +- [x] Run `go mod tidy` + +### Phase 2: Bug Fixes & Code Smells +- [x] Add missing error check in `request.go` +- [x] Replace panics with errors in `crypto.go` +- [x] Update callers of `formatMessage()` and `generateNonce()` +- [x] Fix typo and use fmt.Errorf in `encoder.go` +- [x] Simplify boolean returns in `util.go` + +### Phase 3: ioutil Removal +- [x] Replace all `ioutil.ReadAll` with `io.ReadAll` in source files +- [x] Update documentation examples in `client.go` +- [x] Update README.md examples + +### Phase 4: Breaking API Changes +- [x] Add `context.Context` parameter to `request()` function +- [x] Add `context.Context` to all public HTTP methods +- [x] Update all internal callers +- [x] Remove deprecated methods (`TriggerExclusive`, etc.) +- [x] Remove deprecated `EncryptionMasterKey` field and logic +- [x] Update all tests for new signatures + +### Phase 5: Documentation +- [x] Update README.md with new Go version requirement +- [x] Update README.md examples with context usage +- [x] Write CHANGELOG.md entry for v6.0.0 +- [x] Update code documentation/comments + +### Phase 6: Verification +- [x] Run `go build ./...` +- [x] Run `go test ./...` +- [x] Run `go vet ./...` +- [ ] Run `staticcheck ./...` (if available) +- [ ] Verify GitHub Actions pass + +--- + +## 7. Migration Guide (for CHANGELOG.md) + +```markdown +## 6.0.0 - [DATE] + +### ⚠️ Breaking Changes + +#### Minimum Go Version +- **BREAKING**: Now requires Go 1.26 or later (previously Go 1.11+) + +#### Context Support +- **BREAKING**: All HTTP methods now require `context.Context` as the first parameter: + - `Trigger(ctx, channel, event, data)` + - `TriggerWithParams(ctx, channel, event, data, params)` + - `TriggerMulti(ctx, channels, event, data)` + - `TriggerMultiWithParams(ctx, channels, event, data, params)` + - `TriggerBatch(ctx, batch)` + - `SendToUser(ctx, userId, event, data)` + - `Channels(ctx, params)` + - `Channel(ctx, name, params)` + - `GetChannelUsers(ctx, name)` + +#### Removed Deprecated APIs +- **BREAKING**: Removed `TriggerExclusive()` - use `TriggerWithParams()` with `SocketID` parameter +- **BREAKING**: Removed `TriggerMultiExclusive()` - use `TriggerMultiWithParams()` with `SocketID` parameter +- **BREAKING**: Removed `AuthenticatePrivateChannel()` - use `AuthorizePrivateChannel()` +- **BREAKING**: Removed `AuthenticatePresenceChannel()` - use `AuthorizePresenceChannel()` +- **BREAKING**: Removed `EncryptionMasterKey` field - use `EncryptionMasterKeyBase64` + +### Changed +- Replaced deprecated `io/ioutil` with `io` package +- Updated `golang.org/x/crypto` to v0.49.0 +- Updated `testify` to v1.11.1 with new import path `github.com/stretchr/testify` +- Improved error handling (no more panics in library code) +- Fixed typo: "Paramater" → "Parameter" in error messages + +### Fixed +- Added missing error check after `http.NewRequest` in request handling +``` + +--- + +## 8. Risks & Considerations + +### Breaking Change Impact +- All existing users will need to update their code to add `context.Context` parameters +- Users relying on deprecated methods must migrate before upgrading + +### MD5 Usage +- `crypto.go` uses MD5 for body hashing (`md5Signature`) +- This is likely a **Pusher API requirement**, not a security issue +- Verify with Pusher API docs before considering any changes + +### Testify Migration +- Import path change from `gopkg.in/stretchr/testify.v1` to `github.com/stretchr/testify` +- All test files need updating + +--- + +## 9. Testing Checklist + +- [ ] All existing tests pass with new changes +- [ ] Context cancellation works correctly +- [ ] Context timeout works correctly +- [ ] Error handling in crypto functions works +- [ ] Encrypted channels still work +- [ ] Webhook validation still works +- [ ] All example code in README compiles +- [ ] GitHub Actions CI passes on Go 1.25 and 1.26 diff --git a/README.md b/README.md index 78881c7..69d20a1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Pusher Channels HTTP Go Library -[![Build Status](https://github.com/pusher/pusher-http-go/workflows/Tests/badge.svg)](https://github.com/pusher/pusher-http-go/actions?query=workflow%3ATests+branch%3Amaster) [![Coverage Status](https://coveralls.io/repos/github/pusher/pusher-http-go/badge.svg?branch=master)](https://coveralls.io/github/pusher/pusher-http-go?branch=master) [![Go Reference](https://pkg.go.dev/badge/github.com/pusher/pusher-http-go/v5.svg)](https://pkg.go.dev/github.com/pusher/pusher-http-go/v5) +[![Build Status](https://github.com/pusher/pusher-http-go/workflows/Tests/badge.svg)](https://github.com/pusher/pusher-http-go/actions?query=workflow%3ATests+branch%3Amaster) [![Coverage Status](https://coveralls.io/repos/github/pusher/pusher-http-go/badge.svg?branch=master)](https://coveralls.io/github/pusher/pusher-http-go?branch=master) [![Go Reference](https://pkg.go.dev/badge/github.com/pusher/pusher-http-go/v6.svg)](https://pkg.go.dev/github.com/pusher/pusher-http-go/v6) The Golang library for interacting with the Pusher Channels HTTP API. @@ -10,7 +10,7 @@ Register for free at and use the application crede ## Supported Platforms -* Go - supports **Go 1.11 or greater**. +* Go - supports **Go 1.26 or greater**. ## Table of Contents @@ -33,7 +33,7 @@ Register for free at and use the application crede ## Installation ```sh -$ go get github.com/pusher/pusher-http-go/v5 +$ go get github.com/pusher/pusher-http-go/v6 ``` ## Getting Started @@ -42,7 +42,9 @@ $ go get github.com/pusher/pusher-http-go/v5 package main import ( - "github.com/pusher/pusher-http-go/v5" + "context" + + "github.com/pusher/pusher-http-go/v6" ) func main(){ @@ -57,7 +59,8 @@ func main(){ data := map[string]string{"message": "hello world"} // trigger an event on a channel, along with a data payload - err := pusherClient.Trigger("my-channel", "my_event", data) + ctx := context.Background() + err := pusherClient.Trigger(ctx, "my-channel", "my_event", data) // All trigger methods return an error object, it's worth at least logging this! if err != nil { @@ -188,7 +191,7 @@ import ( "appengine" "appengine/urlfetch" "fmt" - "github.com/pusher/pusher-http-go/v5" + "github.com/pusher/pusher-http-go/v6" "net/http" ) @@ -207,7 +210,7 @@ func handler(w http.ResponseWriter, r *http.Request) { HTTPClient: urlfetchClient, } - pusherClient.Trigger("my-channel", "my_event", map[string]string{"message": "hello world"}) + pusherClient.Trigger(r.Context(), "my-channel", "my_event", map[string]string{"message": "hello world"}) fmt.Fprint(w, "Hello, world!") } @@ -238,6 +241,7 @@ Note: `Info` is part of an [experimental feature](https://pusher.com/docs/lab#ex | Argument |Description | | :-: | :-: | +| ctx `context.Context` | A context for the request. | | channel `string` | The name of the channel you wish to trigger on. | | event `string` | The name of the event you wish to trigger. | | data `interface{}` | The payload you wish to send. Must be marshallable into JSON. | @@ -245,8 +249,9 @@ Note: `Info` is part of an [experimental feature](https://pusher.com/docs/lab#ex ###### Example ```go +ctx := context.Background() data := map[string]string{"hello": "world"} -pusherClient.Trigger("greeting_channel", "say_hello", data) +pusherClient.Trigger(ctx, "greeting_channel", "say_hello", data) ``` ##### `func (c *Client) TriggerWithParams` @@ -256,6 +261,7 @@ The complete list of parameters are documented [here](https://pusher.com/docs/ch | Argument |Description | | :-: | :-: | +| ctx `context.Context` | A context for the request. | | channel `string` | The name of the channel you wish to trigger on. | | event `string` | The name of the event you wish to trigger. | | data `interface{}` | The payload you wish to send. Must be marshallable into JSON. | @@ -269,11 +275,12 @@ The complete list of parameters are documented [here](https://pusher.com/docs/ch ###### Example ```go +ctx := context.Background() data := map[string]string{"hello": "world"} socketID := "1234.12" attributes := "user_count" params := pusher.TriggerParams{SocketID: &socketID, Info: &attributes} -channels, err := pusherClient.TriggerWithParams("presence-chatroom", "say_hello", data, params) +channels, err := pusherClient.TriggerWithParams(ctx, "presence-chatroom", "say_hello", data, params) // channels => &{Channels:map[presence-chatroom:{UserCount:4}]} ``` @@ -284,6 +291,7 @@ channels, err := pusherClient.TriggerWithParams("presence-chatroom", "say_hello" | Argument | Description | | :-: | :-: | +| ctx `context.Context` | A context for the request. | | channels `[]string` | A slice of channel names you wish to send an event on. The maximum length is 10. | | event `string` | As above. | | data `interface{}` | As above. | @@ -291,13 +299,15 @@ channels, err := pusherClient.TriggerWithParams("presence-chatroom", "say_hello" ###### Example ```go -pusherClient.TriggerMulti([]string{"a_channel", "another_channel"}, "event", data) +ctx := context.Background() +pusherClient.TriggerMulti(ctx, []string{"a_channel", "another_channel"}, "event", data) ``` ##### `func (c. *Client) TriggerMultiWithParams` | Argument | Description | | :-: | :-: | +| ctx `context.Context` | A context for the request. | | channels `[]string` | A slice of channel names you wish to send an event on. The maximum length is 10. | | event `string` | As above. | | data `interface{}` | As above. | @@ -311,11 +321,12 @@ pusherClient.TriggerMulti([]string{"a_channel", "another_channel"}, "event", dat ###### Example ```go +ctx := context.Background() data := map[string]string{"hello": "world"} socketID := "1234.12" attributes := "user_count" params := pusher.TriggerParams{SocketID: &socketID, Info: &attributes} -channels, err := pusherClient.TriggerMultiWithParams([]string{"presence-chatroom", "presence-notifications"}, "event", data, params) +channels, err := pusherClient.TriggerMultiWithParams(ctx, []string{"presence-chatroom", "presence-notifications"}, "event", data, params) // channels => &{Channels:map[presence-chatroom:{UserCount:4} presence-notifications:{UserCount:31}]} ``` @@ -326,6 +337,7 @@ channels, err := pusherClient.TriggerMultiWithParams([]string{"presence-chatroom | Argument | Description | | :-: | :-: | +| ctx `context.Context` | A context for the request. | | batch `[]Event` | A list of events to publish | | Return Value | Description | @@ -352,13 +364,14 @@ Note: `Info` is part of an [experimental feature](https://pusher.com/docs/lab#ex ###### Example ```go +ctx := context.Background() socketID := "1234.12" attributes := "user_count" batch := []pusher.Event{ { Channel: "a-channel", Name: "event", Data: "hello world" }, { Channel: "presence-b-channel", Name: "event", Data: "hi my name is bob", SocketID: &socketID, Info: &attributes }, } -response, err := pusherClient.TriggerBatch(batch) +response, err := pusherClient.TriggerBatch(ctx, batch) for i, attributes := range response.Batch { if attributes.UserCount != nil { @@ -378,6 +391,7 @@ for i, attributes := range response.Batch { | Argument |Description | | :-: | :-: | +| ctx `context.Context` | A context for the request. | | userId `string` | The id of the user who should receive the event. | | event `string` | The name of the event you wish to trigger. | | data `interface{}` | The payload you wish to send. Must be marshallable into JSON. | @@ -385,8 +399,9 @@ for i, attributes := range response.Batch { ###### Example ```go +ctx := context.Background() data := map[string]string{"hello": "world"} -pusherClient.SendToUser("user123", "say_hello", data) +pusherClient.SendToUser(ctx, "user123", "say_hello", data) ``` ### Authenticating Users @@ -417,7 +432,7 @@ userData := map[string]interface{} { "id": "1234", "twitter": "jamiepatel" } ```go func pusherUserAuth(res http.ResponseWriter, req *http.Request) { - params, _ := ioutil.ReadAll(req.Body) + params, _ := io.ReadAll(req.Body) userData := map[string]interface{} { "id": "1234", "twitter": "jamiepatel" } response, err := pusherClient.AuthenticateUser(params, userData) if err != nil { @@ -460,7 +475,7 @@ For more information see our [docs](http://pusher.com/docs/authorizing_users). ```go func pusherAuth(res http.ResponseWriter, req *http.Request) { - params, _ := ioutil.ReadAll(req.Body) + params, _ := io.ReadAll(req.Body) response, err := pusherClient.AuthorizePrivateChannel(params) if err != nil { panic(err) @@ -533,7 +548,7 @@ type MemberData struct { ###### Example ```go -params, _ := ioutil.ReadAll(req.Body) +params, _ := io.ReadAll(req.Body) presenceData := pusher.MemberData{ UserID: "1", @@ -561,6 +576,7 @@ This library allows you to query our API to retrieve information about your appl | Argument | Description | | :-: | :-: | +| ctx `context.Context` | A context for the request. | | params `ChannelsParams` | The query options. The field `FilterByPrefix` will filter the returned channels. To get the number of users subscribed to a presence-channel, specify an the `Info` field with value `"user_count"`. Pass in `nil` if you do not wish to specify any query attributes. | | Return Value | Description | @@ -598,10 +614,11 @@ type ChannelListItem struct { ###### Example ```go +ctx := context.Background() prefixFilter := "presence-" attributes := "user_count" params := pusher.ChannelsParams{FilterByPrefix: &prefixFilter, Info: &attributes} -channels, err := pusherClient.Channels(params) +channels, err := pusherClient.Channels(ctx, params) // channels => &{Channels:map[presence-chatroom:{UserCount:4} presence-notifications:{UserCount:31}]} ``` @@ -612,6 +629,7 @@ channels, err := pusherClient.Channels(params) | Argument | Description | | :-: | :-: | +| ctx `context.Context` | A context for the request. | | name `string` | The name of the channel | | params `ChannelParams` | The query options. The field `Info` can have comma-separated values of `"user_count"`, for presence-channels, and `"subscription_count"`, for all-channels. To use the `"subscription_count"` value, first check the "Enable subscription counting" checkbox in your App Settings on [your Pusher Channels dashboard](https://dashboard.pusher.com). Pass in `nil` if you do not wish to specify any query attributes. | @@ -644,9 +662,10 @@ type Channel struct { ###### Example ```go +ctx := context.Background() attributes := "user_count,subscription_count" params := pusher.ChannelParams{Info: &attributes} -channel, err := client.Channel("presence-chatroom", params) +channel, err := client.Channel(ctx, "presence-chatroom", params) // channel => &{Name:presence-chatroom Occupied:true UserCount:42 SubscriptionCount:42} ``` @@ -657,6 +676,7 @@ channel, err := client.Channel("presence-chatroom", params) | Argument | Description | | :-: | :-: | +| ctx `context.Context` | A context for the request. | | name `string` | The channel name | | Return Value | Description | @@ -685,7 +705,8 @@ type User struct { ###### Example ```go -users, err := pusherClient.GetChannelUsers("presence-chatroom") +ctx := context.Background() +users, err := pusherClient.GetChannelUsers(ctx, "presence-chatroom") // users => &{List:[{ID:13} {ID:90}]} ``` @@ -735,7 +756,7 @@ type WebhookEvent struct { ```go func pusherWebhook(res http.ResponseWriter, req *http.Request) { - body, _ := ioutil.ReadAll(req.Body) + body, _ := io.ReadAll(req.Body) webhook, err := pusherClient.Webhook(req.Header, body) if err != nil { fmt.Println("Webhook is invalid :(") diff --git a/channel_authentication_test.go b/channel_authentication_test.go index e26957d..20ff9a4 100644 --- a/channel_authentication_test.go +++ b/channel_authentication_test.go @@ -3,7 +3,7 @@ package pusher import ( "testing" - "gopkg.in/stretchr/testify.v1/assert" + "github.com/stretchr/testify/assert" ) func setUpAuthClient() Client { @@ -17,7 +17,7 @@ func TestPrivateChannelAuthentication(t *testing.T) { client := setUpAuthClient() postParams := []byte("channel_name=private-foobar&socket_id=1234.1234") expected := `{"auth":"278d425bdf160c739803:58df8b0c36d6982b82c3ecf6b4662e34fe8c25bba48f5369f135bf843651c3a4"}` - result, err := client.AuthenticatePrivateChannel(postParams) + result, err := client.AuthorizePrivateChannel(postParams) assert.Equal(t, expected, string(result)) assert.NoError(t, err) } @@ -25,7 +25,7 @@ func TestPrivateChannelAuthentication(t *testing.T) { func TestPrivateChannelAuthenticationWrongParams(t *testing.T) { client := setUpAuthClient() postParams := []byte("hello=hi&two=3") - _, err := client.AuthenticatePrivateChannel(postParams) + _, err := client.AuthorizePrivateChannel(postParams) assert.Error(t, err) } @@ -34,7 +34,7 @@ func TestPresenceChannelAuthentication(t *testing.T) { postParams := []byte("channel_name=presence-foobar&socket_id=1234.1234") presenceData := MemberData{UserID: "10", UserInfo: map[string]string{"name": "Mr. Pusher"}} expected := `{"auth":"278d425bdf160c739803:48dac51d2d7569e1e9c0f48c227d4b26f238fa68e5c0bb04222c966909c4f7c4","channel_data":"{\"user_id\":\"10\",\"user_info\":{\"name\":\"Mr. Pusher\"}}"}` - result, err := client.AuthenticatePresenceChannel(postParams, presenceData) + result, err := client.AuthorizePresenceChannel(postParams, presenceData) assert.Equal(t, expected, string(result)) assert.NoError(t, err) } @@ -42,7 +42,7 @@ func TestPresenceChannelAuthentication(t *testing.T) { func TestAuthSocketIDValidation(t *testing.T) { client := setUpAuthClient() postParams := []byte("channel_name=private-foobar&socket_id=12341234") - result, err := client.AuthenticatePrivateChannel(postParams) + result, err := client.AuthorizePrivateChannel(postParams) assert.Nil(t, result) assert.Error(t, err) } diff --git a/client.go b/client.go index 11fe228..9314606 100644 --- a/client.go +++ b/client.go @@ -1,6 +1,7 @@ package pusher import ( + "context" "encoding/base64" "encoding/json" "errors" @@ -17,7 +18,7 @@ var pusherPathRegex = regexp.MustCompile("^/apps/([0-9]+)$") var maxTriggerableChannels = 100 const ( - libraryVersion = "5.1.1" + libraryVersion = "6.0.0" libraryName = "pusher-http-go" ) @@ -55,7 +56,6 @@ type Client struct { Secure bool // true for HTTPS Cluster string HTTPClient *http.Client - EncryptionMasterKey string // deprecated EncryptionMasterKeyBase64 string // for E2E OverrideMaxMessagePayloadKB int // set the agreed Pusher message limit increase validatedEncryptionMasterKey *[]byte // parsed key for use @@ -123,24 +123,25 @@ func (c *Client) requestClient() *http.Client { return c.HTTPClient } -func (c *Client) request(method, url string, body []byte) ([]byte, error) { - return request(c.requestClient(), method, url, body) +func (c *Client) request(ctx context.Context, method, url string, body []byte) ([]byte, error) { + return request(ctx, c.requestClient(), method, url, body) } /* Trigger triggers an event to the Pusher API. It is possible to trigger an event on one or more channels. Channel names can -contain only characters which are alphanumeric, `_` or `-`` and have +contain only characters which are alphanumeric, `_` or `-“ and have to be at most 200 characters long. Event name can be at most 200 characters long too. -Pass in the channel's name, the event's name, and a data payload. The data payload must -be marshallable into JSON. +Pass in a context, the channel's name, the event's name, and a data payload. +The data payload must be marshallable into JSON. + ctx := context.Background() data := map[string]string{"hello": "world"} - client.Trigger("greeting_channel", "say_hello", data) + client.Trigger(ctx, "greeting_channel", "say_hello", data) */ -func (c *Client) Trigger(channel string, eventName string, data interface{}) error { - _, err := c.validateChannelsAndTrigger([]string{channel}, eventName, data, TriggerParams{}) +func (c *Client) Trigger(ctx context.Context, channel string, eventName string, data interface{}) error { + _, err := c.validateChannelsAndTrigger(ctx, []string{channel}, eventName, data, TriggerParams{}) return err } @@ -179,31 +180,34 @@ parameters to be passed in. See: https://pusher.com/docs/channels/library_auth_reference/rest-api#request for a complete list. + ctx := context.Background() data := map[string]string{"hello": "world"} socketID := "1234.12" attributes := "user_count" params := pusher.TriggerParams{SocketID: &socketID, Info: &attributes} - channels, err := client.Trigger("greeting_channel", "say_hello", data, params) + channels, err := client.TriggerWithParams(ctx, "greeting_channel", "say_hello", data, params) //channels=> &{Channels:map[presence-chatroom:{UserCount:4} presence-notifications:{UserCount:31}]} */ func (c *Client) TriggerWithParams( + ctx context.Context, channel string, eventName string, data interface{}, params TriggerParams, ) (*TriggerChannelsList, error) { - return c.validateChannelsAndTrigger([]string{channel}, eventName, data, params) + return c.validateChannelsAndTrigger(ctx, []string{channel}, eventName, data, params) } /* TriggerMulti is the same as `client.Trigger`, except one passes in a slice of `channels` as the first parameter. The maximum length of channels is 100. - client.TriggerMulti([]string{"a_channel", "another_channel"}, "event", data) + ctx := context.Background() + client.TriggerMulti(ctx, []string{"a_channel", "another_channel"}, "event", data) */ -func (c *Client) TriggerMulti(channels []string, eventName string, data interface{}) error { - _, err := c.validateChannelsAndTrigger(channels, eventName, data, TriggerParams{}) +func (c *Client) TriggerMulti(ctx context.Context, channels []string, eventName string, data interface{}) error { + _, err := c.validateChannelsAndTrigger(ctx, channels, eventName, data, TriggerParams{}) return err } @@ -213,71 +217,43 @@ allows additional parameters to be specified in the same way as `client.TriggerWithParams`. */ func (c *Client) TriggerMultiWithParams( + ctx context.Context, channels []string, eventName string, data interface{}, params TriggerParams, ) (*TriggerChannelsList, error) { - return c.validateChannelsAndTrigger(channels, eventName, data, params) -} - -/* -TriggerExclusive triggers an event excluding a recipient whose connection has -the `socket_id` you specify here from receiving the event. -You can read more here: http://pusher.com/docs/duplicates. - - client.TriggerExclusive("a_channel", "event", data, "123.12") - -Deprecated: use TriggerWithParams instead. -*/ -func (c *Client) TriggerExclusive(channel string, eventName string, data interface{}, socketID string) error { - params := TriggerParams{SocketID: &socketID} - _, err := c.validateChannelsAndTrigger([]string{channel}, eventName, data, params) - return err -} - -/* -TriggerMultiExclusive triggers an event to multiple channels excluding a -recipient whose connection has the `socket_id` you specify here from receiving -the event on any of the channels. - - client.TriggerMultiExclusive([]string{"a_channel", "another_channel"}, "event", data, "123.12") - -Deprecated: use TriggerMultiWithParams instead. -*/ -func (c *Client) TriggerMultiExclusive(channels []string, eventName string, data interface{}, socketID string) error { - params := TriggerParams{SocketID: &socketID} - _, err := c.validateChannelsAndTrigger(channels, eventName, data, params) - return err + return c.validateChannelsAndTrigger(ctx, channels, eventName, data, params) } /* SendToUser triggers an event to a specific user. -Pass in the user id, the event's name, and a data payload. The data payload must -be marshallable into JSON. +Pass in a context, the user id, the event's name, and a data payload. +The data payload must be marshallable into JSON. + ctx := context.Background() data := map[string]string{"hello": "world"} - client.SendToUser("user123", "say_hello", data) + client.SendToUser(ctx, "user123", "say_hello", data) */ -func (c *Client) SendToUser(userId string, eventName string, data interface{}) error { +func (c *Client) SendToUser(ctx context.Context, userId string, eventName string, data interface{}) error { if !validUserId(userId) { return fmt.Errorf("User id '%s' is invalid", userId) } - _, err := c.trigger([]string{"#server-to-user-" + userId}, eventName, data, TriggerParams{}) + _, err := c.trigger(ctx, []string{"#server-to-user-" + userId}, eventName, data, TriggerParams{}) return err } -func (c *Client) validateChannelsAndTrigger(channels []string, eventName string, data interface{}, params TriggerParams) (*TriggerChannelsList, error) { +func (c *Client) validateChannelsAndTrigger(ctx context.Context, channels []string, eventName string, data interface{}, params TriggerParams) (*TriggerChannelsList, error) { if len(channels) > maxTriggerableChannels { return nil, fmt.Errorf("You cannot trigger on more than %d channels at once", maxTriggerableChannels) } if !channelsAreValid(channels) { return nil, errors.New("At least one of your channels' names are invalid") } - return c.trigger(channels, eventName, data, params) + return c.trigger(ctx, channels, eventName, data, params) } -func (c *Client) trigger(channels []string, eventName string, data interface{}, params TriggerParams) (*TriggerChannelsList, error) { +func (c *Client) trigger(ctx context.Context, channels []string, eventName string, data interface{}, params TriggerParams) (*TriggerChannelsList, error) { hasEncryptedChannel := false for _, channel := range channels { if isEncryptedChannel(channel) { @@ -306,7 +282,7 @@ func (c *Client) trigger(channels []string, eventName string, data interface{}, if err != nil { return nil, err } - response, err := c.request("POST", triggerURL, payload) + response, err := c.request(ctx, "POST", triggerURL, payload) if err != nil { return nil, err } @@ -329,14 +305,15 @@ type Event struct { /* TriggerBatch triggers multiple events on multiple channels in a single call: + ctx := context.Background() info := "subscription_count" socketID := "1234.12" - client.TriggerBatch([]pusher.Event{ + client.TriggerBatch(ctx, []pusher.Event{ { Channel: "donut-1", Name: "ev1", Data: "d1", SocketID: socketID, Info: &info }, { Channel: "private-encrypted-secretdonut", Name: "ev2", Data: "d2", SocketID: socketID, Info: &info }, }) */ -func (c *Client) TriggerBatch(batch []Event) (*TriggerBatchChannelsList, error) { +func (c *Client) TriggerBatch(ctx context.Context, batch []Event) (*TriggerBatchChannelsList, error) { hasEncryptedChannel := false // validate every channel name and every sockedID (if present) in batch for _, event := range batch { @@ -364,7 +341,7 @@ func (c *Client) TriggerBatch(batch []Event) (*TriggerBatchChannelsList, error) if err != nil { return nil, err } - response, err := c.request("POST", triggerURL, payload) + response, err := c.request(ctx, "POST", triggerURL, payload) if err != nil { return nil, err } @@ -398,20 +375,21 @@ func (params ChannelsParams) toMap() map[string]string { /* Channels returns a list of all the channels in an application. + ctx := context.Background() prefixFilter := "presence-" attributes := "user_count" params := pusher.ChannelsParams{FilterByPrefix: &prefixFilter, Info: &attributes} - channels, err := client.Channels(params) + channels, err := client.Channels(ctx, params) //channels=> &{Channels:map[presence-chatroom:{UserCount:4} presence-notifications:{UserCount:31} ]} */ -func (c *Client) Channels(params ChannelsParams) (*ChannelsList, error) { +func (c *Client) Channels(ctx context.Context, params ChannelsParams) (*ChannelsList, error) { path := fmt.Sprintf("/apps/%s/channels", c.AppID) u, err := createRequestURL("GET", c.Host, path, c.Key, c.Secret, authTimestamp(), c.Secure, nil, params.toMap(), c.Cluster) if err != nil { return nil, err } - response, err := c.request("GET", u, nil) + response, err := c.request(ctx, "GET", u, nil) if err != nil { return nil, err } @@ -441,19 +419,20 @@ func (params ChannelParams) toMap() map[string]string { /* Channel allows you to get the state of a single channel. + ctx := context.Background() attributes := "user_count,subscription_count" params := pusher.ChannelParams{Info: &attributes} - channel, err := client.Channel("presence-chatroom", params) + channel, err := client.Channel(ctx, "presence-chatroom", params) //channel=> &{Name:presence-chatroom Occupied:true UserCount:42 SubscriptionCount:42} */ -func (c *Client) Channel(name string, params ChannelParams) (*Channel, error) { +func (c *Client) Channel(ctx context.Context, name string, params ChannelParams) (*Channel, error) { path := fmt.Sprintf("/apps/%s/channels/%s", c.AppID, name) u, err := createRequestURL("GET", c.Host, path, c.Key, c.Secret, authTimestamp(), c.Secure, nil, params.toMap(), c.Cluster) if err != nil { return nil, err } - response, err := c.request("GET", u, nil) + response, err := c.request(ctx, "GET", u, nil) if err != nil { return nil, err } @@ -464,17 +443,18 @@ func (c *Client) Channel(name string, params ChannelParams) (*Channel, error) { GetChannelUsers returns a list of users in a presence-channel by passing to this method the channel name. - users, err := client.GetChannelUsers("presence-chatroom") + ctx := context.Background() + users, err := client.GetChannelUsers(ctx, "presence-chatroom") //users=> &{List:[{ID:13} {ID:90}]} */ -func (c *Client) GetChannelUsers(name string) (*Users, error) { +func (c *Client) GetChannelUsers(ctx context.Context, name string) (*Users, error) { path := fmt.Sprintf("/apps/%s/channels/%s/users", c.AppID, name) u, err := createRequestURL("GET", c.Host, path, c.Key, c.Secret, authTimestamp(), c.Secure, nil, nil, c.Cluster) if err != nil { return nil, err } - response, err := c.request("GET", u, nil) + response, err := c.request(ctx, "GET", u, nil) if err != nil { return nil, err } @@ -498,7 +478,7 @@ to send back to the client. func pusherUserAuth(res http.ResponseWriter, req *http.Request) { - params, _ := ioutil.ReadAll(req.Body) + params, _ := io.ReadAll(req.Body) userData := map[string]interface{} { "id": "1234", "twitter": "jamiepatel" } response, err := client.AuthenticateUser(params, userData) if err != nil { @@ -556,7 +536,7 @@ to send back to the client. func pusherAuth(res http.ResponseWriter, req *http.Request) { - params, _ := ioutil.ReadAll(req.Body) + params, _ := io.ReadAll(req.Body) response, err := client.AuthorizePrivateChannel(params) if err != nil { panic(err) @@ -574,17 +554,6 @@ func (c *Client) AuthorizePrivateChannel(params []byte) (response []byte, err er return c.authorizeChannel(params, nil) } -/* -AuthenticatePrivateChannel allows you to authorize a users subscription to a -private channel. It returns an authorization signature to send back to the client -and authorize them. - -Deprecated: use AuthorizePrivateChannel instead. -*/ -func (c *Client) AuthenticatePrivateChannel(params []byte) (response []byte, err error) { - return c.authorizeChannel(params, nil) -} - /* AuthorizePresenceChannel allows you to authorize a users subscription to a presence channel. It returns an authorization signature to send back to the client @@ -593,7 +562,7 @@ optionally, custom data. In this library, one does this by passing a `pusher.MemberData` instance. - params, _ := ioutil.ReadAll(req.Body) + params, _ := io.ReadAll(req.Body) presenceData := pusher.MemberData{ UserID: "1", @@ -613,18 +582,6 @@ func (c *Client) AuthorizePresenceChannel(params []byte, member MemberData) (res return c.authorizeChannel(params, &member) } -/* -AuthenticatePresenceChannel allows you to authorize a users subscription to a -presence channel. It returns an authorization signature to send back to the client -and authorize them. In order to identify a user, clients are sent a user_id and, -optionally, custom data. - -Deprecated: use AuthorizePresenceChannel instead. -*/ -func (c *Client) AuthenticatePresenceChannel(params []byte, member MemberData) (response []byte, err error) { - return c.authorizeChannel(params, &member) -} - func (c *Client) authorizeChannel(params []byte, member *MemberData) (response []byte, err error) { channelName, socketID, err := parseChannelAuthorizationRequestParams(params) if err != nil { @@ -687,7 +644,7 @@ error will be passed. func pusherWebhook(res http.ResponseWriter, req *http.Request) { - body, _ := ioutil.ReadAll(req.Body) + body, _ := io.ReadAll(req.Body) webhook, err := client.Webhook(req.Header, body) if err != nil { fmt.Println("Webhook is invalid :(") @@ -727,20 +684,6 @@ func (c *Client) encryptionMasterKey() ([]byte, error) { return *(c.validatedEncryptionMasterKey), nil } - if c.EncryptionMasterKey != "" && c.EncryptionMasterKeyBase64 != "" { - return nil, errors.New("Do not specify both EncryptionMasterKey and EncryptionMasterKeyBase64. EncryptionMasterKey is deprecated, specify only EncryptionMasterKeyBase64") - } - - if c.EncryptionMasterKey != "" { - if len(c.EncryptionMasterKey) != 32 { - return nil, errors.New("EncryptionMasterKey must be 32 bytes. It is also deprecated, use EncryptionMasterKeyBase64") - } - - keyBytes := []byte(c.EncryptionMasterKey) - c.validatedEncryptionMasterKey = &keyBytes - return keyBytes, nil - } - if c.EncryptionMasterKeyBase64 != "" { keyBytes, err := base64.StdEncoding.DecodeString(c.EncryptionMasterKeyBase64) if err != nil { diff --git a/client_test.go b/client_test.go index 53c04e6..e79f0db 100644 --- a/client_test.go +++ b/client_test.go @@ -1,9 +1,10 @@ package pusher import ( + "context" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" "net/url" @@ -12,7 +13,7 @@ import ( "testing" "time" - "gopkg.in/stretchr/testify.v1/assert" + "github.com/stretchr/testify/assert" ) func TestSendToUserSuccessCase(t *testing.T) { @@ -37,7 +38,7 @@ func TestSendToUserSuccessCase(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - err := client.SendToUser("123456", "test", "yolo") + err := client.SendToUser(context.Background(), "123456", "test", "yolo") assert.NoError(t, err) } @@ -49,7 +50,7 @@ func TestSendToUserRejected(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - err := client.SendToUser("", "test", "yolo") + err := client.SendToUser(context.Background(), "", "test", "yolo") assert.Error(t, err) assert.Contains(t, err.Error(), "User id '' is invalid") } @@ -76,7 +77,7 @@ func TestTriggerSuccessCase(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - err := client.Trigger("test_channel", "test", "yolo") + err := client.Trigger(context.Background(), "test_channel", "test", "yolo") assert.NoError(t, err) } @@ -102,7 +103,7 @@ func TestTriggerWithStructSuccessCase(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - err := client.Trigger("test_channel", "test", struct{ Key string }{Key: "value"}) + err := client.Trigger(context.Background(), "test_channel", "test", struct{ Key string }{Key: "value"}) assert.NoError(t, err) } @@ -111,7 +112,7 @@ func TestTriggerWithParamsSuccessCase(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(200) testJSON := "{}" - fmt.Fprintf(res, testJSON) + fmt.Fprint(res, testJSON) assert.Equal(t, "POST", req.Method) expectedBody := map[string]interface{}{"name": "test", "channels": []interface{}{"test_channel"}, "data": "yolo"} @@ -131,7 +132,7 @@ func TestTriggerWithParamsSuccessCase(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} // Empty parameters - channels, err := client.TriggerWithParams("test_channel", "test", "yolo", TriggerParams{}) + channels, err := client.TriggerWithParams(context.Background(), "test_channel", "test", "yolo", TriggerParams{}) assert.NoError(t, err) expected := &TriggerChannelsList{ @@ -144,7 +145,7 @@ func TestTriggerWithParamsInfoSuccessCase(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(200) testJSON := "{\"channels\":{\"test_channel\":{\"subscription_count\":1}}}" - fmt.Fprintf(res, testJSON) + fmt.Fprint(res, testJSON) assert.Equal(t, "POST", req.Method) expectedBody := map[string]interface{}{"name": "test", "channels": []interface{}{"test_channel"}, "data": "yolo", "info": "subscription_count"} @@ -164,7 +165,7 @@ func TestTriggerWithParamsInfoSuccessCase(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} attributes := "subscription_count" - channels, err := client.TriggerWithParams("test_channel", "test", "yolo", TriggerParams{Info: &attributes}) + channels, err := client.TriggerWithParams(context.Background(), "test_channel", "test", "yolo", TriggerParams{Info: &attributes}) assert.NoError(t, err) expectedSubscriptionCount := 1 @@ -196,7 +197,7 @@ func TestTriggerMultiSuccessCase(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - err := client.TriggerMulti([]string{"test_channel", "other_channel"}, "test", "yolo") + err := client.TriggerMulti(context.Background(), []string{"test_channel", "other_channel"}, "test", "yolo") assert.NoError(t, err) } @@ -214,7 +215,7 @@ func TestTriggerMultiEncryptedRejected(t *testing.T) { Host: u.Host, EncryptionMasterKeyBase64: "ZUhQVldIZzduRkdZVkJzS2pPRkRYV1JyaWJJUjJiMGI=", } - err := client.TriggerMulti([]string{"test_channel", "private-encrypted-other_channel"}, "test", "yolo") + err := client.TriggerMulti(context.Background(), []string{"test_channel", "private-encrypted-other_channel"}, "test", "yolo") assert.Error(t, err) assert.Contains(t, err.Error(), "multiple channels") assert.Contains(t, err.Error(), "encrypted channels") @@ -224,7 +225,7 @@ func TestTriggerMultiWithParamsInfoSuccessCase(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(200) testJSON := "{\"channels\":{\"presence-test_channel\":{\"subscription_count\":2,\"user_count\":1},\"test_channel\":{\"subscription_count\":3}}}" - fmt.Fprintf(res, testJSON) + fmt.Fprint(res, testJSON) assert.Equal(t, "POST", req.Method) expectedBody := map[string]interface{}{"name": "test", "channels": []interface{}{"presence-test_channel", "test_channel"}, "data": "yolo", "info": "user_count,subscription_count"} @@ -244,7 +245,7 @@ func TestTriggerMultiWithParamsInfoSuccessCase(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} attributes := "user_count,subscription_count" - channels, err := client.TriggerMultiWithParams([]string{"presence-test_channel", "test_channel"}, "test", "yolo", TriggerParams{Info: &attributes}) + channels, err := client.TriggerMultiWithParams(context.Background(), []string{"presence-test_channel", "test_channel"}, "test", "yolo", TriggerParams{Info: &attributes}) assert.NoError(t, err) presenceExpectedUserCount := 1 @@ -264,7 +265,7 @@ func TestGetChannelsSuccessCase(t *testing.T) { res.WriteHeader(200) testJSON := "{\"channels\":{\"presence-session-d41a439c438a100756f5-4bf35003e819bb138249-5cbTiUiPNGI\":{\"user_count\":1},\"presence-session-d41a439c438a100756f5-4bf35003e819bb138249-PbZ5E1pP8uF\":{\"user_count\":1},\"presence-session-d41a439c438a100756f5-4bf35003e819bb138249-oz6iqpSxMwG\":{\"user_count\":1}}}" - fmt.Fprintf(res, testJSON) + fmt.Fprint(res, testJSON) assert.Equal(t, "GET", req.Method) })) @@ -272,7 +273,7 @@ func TestGetChannelsSuccessCase(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - channels, err := client.Channels(ChannelsParams{}) + channels, err := client.Channels(context.Background(), ChannelsParams{}) assert.NoError(t, err) expected := &ChannelsList{ @@ -289,7 +290,7 @@ func TestGetChannelSuccess(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(200) testJSON := "{\"user_count\":1,\"occupied\":true,\"subscription_count\":1}" - fmt.Fprintf(res, testJSON) + fmt.Fprint(res, testJSON) assert.Equal(t, "GET", req.Method) })) @@ -297,7 +298,7 @@ func TestGetChannelSuccess(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - channel, err := client.Channel("test_channel", ChannelParams{}) + channel, err := client.Channel(context.Background(), "test_channel", ChannelParams{}) assert.NoError(t, err) expected := &Channel{ @@ -313,7 +314,7 @@ func TestGetChannelUserSuccess(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(200) testJSON := "{\"users\":[{\"id\":\"red\"},{\"id\":\"blue\"}]}" - fmt.Fprintf(res, testJSON) + fmt.Fprint(res, testJSON) assert.Equal(t, "GET", req.Method) })) @@ -321,7 +322,7 @@ func TestGetChannelUserSuccess(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - users, err := client.GetChannelUsers("test_channel") + users, err := client.GetChannelUsers(context.Background(), "test_channel") assert.NoError(t, err) expected := &Users{ @@ -344,12 +345,14 @@ func TestTriggerWithSocketID(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - client.TriggerExclusive("test_channel", "test", "yolo", "1234.12") + socketID := "1234.12" + client.TriggerWithParams(context.Background(), "test_channel", "test", "yolo", TriggerParams{SocketID: &socketID}) } func TestTriggerSocketIDValidation(t *testing.T) { client := Client{AppID: "id", Key: "key", Secret: "secret"} - err := client.TriggerExclusive("test_channel", "test", "yolo", "1234.12:lalala") + socketID := "1234.12:lalala" + _, err := client.TriggerWithParams(context.Background(), "test_channel", "test", "yolo", TriggerParams{SocketID: &socketID}) assert.Error(t, err) } @@ -360,7 +363,7 @@ func TestTriggerBatchSuccess(t *testing.T) { fmt.Fprintf(res, "{}") assert.Equal(t, "POST", req.Method) - actualBody, err := ioutil.ReadAll(req.Body) + actualBody, err := io.ReadAll(req.Body) assert.Equal(t, expectedBody, string(actualBody)) assert.Equal(t, "application/json", req.Header["Content-Type"][0]) assert.Equal(t, "/apps/appid/batch_events", req.URL.Path) @@ -370,7 +373,7 @@ func TestTriggerBatchSuccess(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "appid", Key: "key", Secret: "secret", Host: u.Host} - response, err := client.TriggerBatch([]Event{ + response, err := client.TriggerBatch(context.Background(), []Event{ {Channel: "test_channel", Name: "test", Data: "yolo1"}, {Channel: "test_channel", Name: "test", Data: "yolo2"}, }) @@ -384,10 +387,10 @@ func TestTriggerBatchInfoSuccess(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(200) testJSON := "{\"batch\":[{\"subscription_count\":2,\"user_count\":1},{\"subscription_count\":3}]}" - fmt.Fprintf(res, testJSON) + fmt.Fprint(res, testJSON) assert.Equal(t, "POST", req.Method) - actualBody, err := ioutil.ReadAll(req.Body) + actualBody, err := io.ReadAll(req.Body) assert.Equal(t, expectedBody, string(actualBody)) assert.Equal(t, "application/json", req.Header["Content-Type"][0]) assert.Equal(t, "/apps/appid/batch_events", req.URL.Path) @@ -399,7 +402,7 @@ func TestTriggerBatchInfoSuccess(t *testing.T) { client := Client{AppID: "appid", Key: "key", Secret: "secret", Host: u.Host} presenceChannelInfo := "user_count,subscription_count" channelInfo := "subscription_count" - channels, err := client.TriggerBatch([]Event{ + channels, err := client.TriggerBatch(context.Background(), []Event{ {Channel: "presence-test_channel", Name: "test", Data: "yolo1", Info: &presenceChannelInfo}, {Channel: "test_channel", Name: "test", Data: "yolo2", Info: &channelInfo}, }) @@ -425,7 +428,7 @@ func TestTriggerBatchWithEncryptionMasterKeyNoEncryptedChanSuccess(t *testing.T) fmt.Fprintf(res, "{}") assert.Equal(t, "POST", req.Method) - actualBody, err := ioutil.ReadAll(req.Body) + actualBody, err := io.ReadAll(req.Body) assert.Equal(t, expectedBody, string(actualBody)) assert.Equal(t, "application/json", req.Header["Content-Type"][0]) assert.Equal(t, "/apps/appid/batch_events", req.URL.Path) @@ -434,7 +437,7 @@ func TestTriggerBatchWithEncryptionMasterKeyNoEncryptedChanSuccess(t *testing.T) defer server.Close() u, _ := url.Parse(server.URL) client := Client{AppID: "appid", Key: "key", Secret: "secret", EncryptionMasterKeyBase64: "ZUhQVldIZzduRkdZVkJzS2pPRkRYV1JyaWJJUjJiMGI=", Host: u.Host} - response, err := client.TriggerBatch([]Event{ + response, err := client.TriggerBatch(context.Background(), []Event{ {Channel: "test_channel", Name: "test", Data: "yolo1"}, {Channel: "test_channel", Name: "test", Data: "yolo2"}, }) @@ -451,7 +454,7 @@ func TestTriggerBatchNoEncryptionMasterKeyWithEncryptedChanFailure(t *testing.T) u, _ := url.Parse(server.URL) client := Client{AppID: "appid", Key: "key", Secret: "secret", Host: u.Host} - _, err := client.TriggerBatch([]Event{ + _, err := client.TriggerBatch(context.Background(), []Event{ {Channel: "test_channel", Name: "test", Data: "yolo1"}, {Channel: "private-encrypted-test_channel", Name: "test", Data: "yolo2"}, }) @@ -466,7 +469,7 @@ func TestTriggerWithEncryptedChanSuccess(t *testing.T) { fmt.Fprintf(res, "{}") assert.Equal(t, "POST", req.Method) - actualBody, err := ioutil.ReadAll(req.Body) + actualBody, err := io.ReadAll(req.Body) assert.Contains(t, string(actualBody), "ciphertext") assert.Contains(t, string(actualBody), "nonce") assert.Equal(t, "application/json", req.Header["Content-Type"][0]) @@ -477,7 +480,7 @@ func TestTriggerWithEncryptedChanSuccess(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "appid", Key: "key", Secret: "secret", EncryptionMasterKeyBase64: "ZUhQVldIZzduRkdZVkJzS2pPRkRYV1JyaWJJUjJiMGI=", Host: u.Host} - err := client.Trigger("private-encrypted-test_channel", "test", "yolo1") + err := client.Trigger(context.Background(), "private-encrypted-test_channel", "test", "yolo1") assert.NoError(t, err) } @@ -487,7 +490,7 @@ func TestTriggerBatchWithEncryptedChanSuccess(t *testing.T) { fmt.Fprintf(res, "{}") assert.Equal(t, "POST", req.Method) - _, err := ioutil.ReadAll(req.Body) + _, err := io.ReadAll(req.Body) assert.Equal(t, "application/json", req.Header["Content-Type"][0]) assert.Equal(t, "/apps/appid/batch_events", req.URL.Path) assert.NoError(t, err) @@ -496,7 +499,7 @@ func TestTriggerBatchWithEncryptedChanSuccess(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "appid", Key: "key", Secret: "secret", EncryptionMasterKeyBase64: "ZUhQVldIZzduRkdZVkJzS2pPRkRYV1JyaWJJUjJiMGI=", Host: u.Host} - response, err := client.TriggerBatch([]Event{ + response, err := client.TriggerBatch(context.Background(), []Event{ {Channel: "test_channel", Name: "test", Data: "yolo1"}, {Channel: "private-encrypted-test_channel", Name: "test", Data: "yolo2"}, }) @@ -511,52 +514,15 @@ func TestTriggerInvalidMasterKey(t *testing.T) { defer server.Close() u, _ := url.Parse(server.URL) - // too short (deprecated) - client := Client{ - AppID: "appid", - Key: "key", - Secret: "secret", - Host: u.Host, - EncryptionMasterKey: "this is 31 bytes 12345678901234", - } - err := client.Trigger("private-encrypted-test_channel", "test", "yolo1") - assert.Error(t, err) - assert.Contains(t, err.Error(), "32 bytes") - - // too long (deprecated) - client = Client{ - AppID: "appid", - Key: "key", - Secret: "secret", - Host: u.Host, - EncryptionMasterKey: "this is 33 bytes 1234567890123456", - } - err = client.Trigger("private-encrypted-test_channel", "test", "yolo1") - assert.Error(t, err) - assert.Contains(t, err.Error(), "32 bytes") - - // both provided - client = Client{ - AppID: "appid", - Key: "key", - Secret: "secret", - Host: u.Host, - EncryptionMasterKey: "this is 32 bytes 123456789012345", - EncryptionMasterKeyBase64: "dGhpcyBpcyAzMiBieXRlcyAxMjM0NTY3ODkwMTIzNDU=", - } - err = client.Trigger("private-encrypted-test_channel", "test", "yolo1") - assert.Error(t, err) - assert.Contains(t, err.Error(), "both") - // too short - client = Client{ + client := Client{ AppID: "appid", Key: "key", Secret: "secret", Host: u.Host, EncryptionMasterKeyBase64: "dGhpcyBpcyAzMSBieXRlcyAxMjM0NTY3ODkwMTIzNA==", } - err = client.Trigger("private-encrypted-test_channel", "test", "yolo1") + err := client.Trigger(context.Background(), "private-encrypted-test_channel", "test", "yolo1") assert.Error(t, err) assert.Contains(t, err.Error(), "32 bytes") @@ -568,7 +534,7 @@ func TestTriggerInvalidMasterKey(t *testing.T) { Host: u.Host, EncryptionMasterKeyBase64: "dGhpcyBpcyAzMiBieXRlcyAxMjM0NTY3ODkwMTIzNDU2", } - err = client.Trigger("private-encrypted-test_channel", "test", "yolo1") + err = client.Trigger(context.Background(), "private-encrypted-test_channel", "test", "yolo1") assert.Error(t, err) assert.Contains(t, err.Error(), "32 bytes") @@ -580,7 +546,7 @@ func TestTriggerInvalidMasterKey(t *testing.T) { Host: u.Host, EncryptionMasterKeyBase64: "dGhp!yBpcyAzMiBieXRlcy#xMjM0NTY3ODkwMTIzNDU=", } - err = client.Trigger("private-encrypted-test_channel", "test", "yolo1") + err = client.Trigger(context.Background(), "private-encrypted-test_channel", "test", "yolo1") assert.Error(t, err) assert.Contains(t, err.Error(), "valid base64") } @@ -593,29 +559,29 @@ func TestAuthenticateUser(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{ - AppID: "appid", - Key: "key", - Secret: "secret", - Host: u.Host, + AppID: "appid", + Key: "key", + Secret: "secret", + Host: u.Host, } var params []byte var userData map[string]interface{} params = []byte("socket_id=12345.12345") - userData = map[string]interface{} {} + userData = map[string]interface{}{} _, err := client.AuthenticateUser(params, userData) assert.Error(t, err) assert.Contains(t, err.Error(), "Missing id in user data") params = []byte("not_socket_id=12345.12345") - userData = map[string]interface{} { "id": "1234" } + userData = map[string]interface{}{"id": "1234"} _, err = client.AuthenticateUser(params, userData) assert.Error(t, err) assert.Contains(t, err.Error(), "socket_id not found") params = []byte("socket_id=12345.12345") - userData = map[string]interface{} { "id": "1234" } + userData = map[string]interface{}{"id": "1234"} var response []byte response, err = client.AuthenticateUser(params, userData) assert.NoError(t, err) @@ -631,52 +597,15 @@ func TestAuthorizeInvalidMasterKey(t *testing.T) { params := []byte("channel_name=private-encrypted-test_channel&socket_id=12345.12345") - // too short (deprecated) - client := Client{ - AppID: "appid", - Key: "key", - Secret: "secret", - Host: u.Host, - EncryptionMasterKey: "this is 31 bytes 12345678901234", - } - _, err := client.AuthorizePrivateChannel(params) - assert.Error(t, err) - assert.Contains(t, err.Error(), "32 bytes") - - // too long (deprecated) - client = Client{ - AppID: "appid", - Key: "key", - Secret: "secret", - Host: u.Host, - EncryptionMasterKey: "this is 33 bytes 1234567890123456", - } - _, err = client.AuthorizePrivateChannel(params) - assert.Error(t, err) - assert.Contains(t, err.Error(), "32 bytes") - - // both provided - client = Client{ - AppID: "appid", - Key: "key", - Secret: "secret", - Host: u.Host, - EncryptionMasterKey: "this is 32 bytes 123456789012345", - EncryptionMasterKeyBase64: "dGhpcyBpcyAzMiBieXRlcyAxMjM0NTY3ODkwMTIzNDU=", - } - _, err = client.AuthorizePrivateChannel(params) - assert.Error(t, err) - assert.Contains(t, err.Error(), "both") - // too short - client = Client{ + client := Client{ AppID: "appid", Key: "key", Secret: "secret", Host: u.Host, EncryptionMasterKeyBase64: "dGhpcyBpcyAzMSBieXRlcyAxMjM0NTY3ODkwMTIzNA==", } - _, err = client.AuthorizePrivateChannel(params) + _, err := client.AuthorizePrivateChannel(params) assert.Error(t, err) assert.Contains(t, err.Error(), "32 bytes") @@ -716,7 +645,7 @@ func TestErrorResponseHandler(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} attributes := "user_count,subscription_count" - channel, err := client.Channel("this_is_not_a_presence_channel", ChannelParams{Info: &attributes}) + channel, err := client.Channel(context.Background(), "this_is_not_a_presence_channel", ChannelParams{Info: &attributes}) assert.Error(t, err) assert.EqualError(t, err, "Status Code: 400 - Cannot retrieve the user count unless the channel is a presence channel") @@ -733,7 +662,7 @@ func TestRequestTimeouts(t *testing.T) { u, _ := url.Parse(server.URL) client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host, HTTPClient: &http.Client{Timeout: time.Millisecond * 100}} - err := client.Trigger("test_channel", "test", "yolo") + err := client.Trigger(context.Background(), "test_channel", "test", "yolo") assert.Error(t, err) } @@ -753,7 +682,7 @@ func TestChannelLengthValidation(t *testing.T) { } client := Client{AppID: "id", Key: "key", Secret: "secret"} - err := client.TriggerMulti(channels, "yolo", "woot") + err := client.TriggerMulti(context.Background(), channels, "yolo", "woot") assert.EqualError(t, err, "You cannot trigger on more than 100 channels at once") } @@ -765,9 +694,9 @@ func TestChannelFormatValidation(t *testing.T) { channel2 += "a" } client := Client{AppID: "id", Key: "key", Secret: "secret"} - err1 := client.Trigger(channel1, "yolo", "w00t") + err1 := client.Trigger(context.Background(), channel1, "yolo", "w00t") - err2 := client.Trigger(channel2, "yolo", "not 19 forever") + err2 := client.Trigger(context.Background(), channel2, "yolo", "not 19 forever") assert.EqualError(t, err1, "At least one of your channels' names are invalid") @@ -778,11 +707,11 @@ func TestChannelFormatValidation(t *testing.T) { func TestDataSizeValidation(t *testing.T) { client := Client{AppID: "id", Key: "key", Secret: "secret"} data := strings.Repeat("a", 20481) - err := client.Trigger("channel", "event", data) + err := client.Trigger(context.Background(), "channel", "event", data) - assert.EqualError(t, err, "Event payload exceeded maximum size (20481 bytes is too much)") + assert.EqualError(t, err, "event payload exceeded maximum size (20481 bytes is too much)") - _, err = client.TriggerBatch([]Event{ + _, err = client.TriggerBatch(context.Background(), []Event{ {Channel: "channel", Name: "event", Data: data}, }) assert.EqualError(t, err, "Data of the event #0 in batch, exceeded maximum size (20481 bytes is too much)") @@ -791,18 +720,18 @@ func TestDataSizeValidation(t *testing.T) { func TestDataSizeOverridenValidation(t *testing.T) { client := Client{AppID: "id", Key: "key", Secret: "secret", OverrideMaxMessagePayloadKB: 80} data := strings.Repeat("a", 81920) - err := client.Trigger("channel", "event", data) + err := client.Trigger(context.Background(), "channel", "event", data) assert.NotContains(t, err.Error(), "\"Event payload exceeded maximum size (81920 bytes is too much)") - _, err = client.TriggerBatch([]Event{ + _, err = client.TriggerBatch(context.Background(), []Event{ {Channel: "channel", Name: "event", Data: data}, }) assert.NotContains(t, err.Error(), "Data of the event #0 in batch, exceeded maximum size (81920 bytes is too much)") data = strings.Repeat("a", 81921) - err = client.Trigger("channel", "event", data) - assert.EqualError(t, err, "Event payload exceeded maximum size (81921 bytes is too much)") + err = client.Trigger(context.Background(), "channel", "event", data) + assert.EqualError(t, err, "event payload exceeded maximum size (81921 bytes is too much)") - _, err = client.TriggerBatch([]Event{ + _, err = client.TriggerBatch(context.Background(), []Event{ {Channel: "channel", Name: "event", Data: data}, }) assert.EqualError(t, err, "Data of the event #0 in batch, exceeded maximum size (81921 bytes is too much)") diff --git a/crypto.go b/crypto.go index 5012f6a..98231e1 100644 --- a/crypto.go +++ b/crypto.go @@ -9,6 +9,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" "io" "strings" @@ -51,39 +52,40 @@ func createAuthMap(key, secret, stringToSign string, sharedSecret string) map[st func md5Signature(body []byte) string { _bodyMD5 := md5.New() - _bodyMD5.Write([]byte(body)) + _bodyMD5.Write(body) return hex.EncodeToString(_bodyMD5.Sum(nil)) } -func encrypt(channel string, data []byte, encryptionKey []byte) string { +func encrypt(channel string, data []byte, encryptionKey []byte) (string, error) { sharedSecret := generateSharedSecret(channel, encryptionKey) - nonce := generateNonce() + nonce, err := generateNonce() + if err != nil { + return "", fmt.Errorf("failed to generate nonce: %w", err) + } nonceB64 := base64.StdEncoding.EncodeToString(nonce[:]) - cipherText := secretbox.Seal([]byte{}, data, &nonce, &sharedSecret) + cipherText := secretbox.Seal(nil, data, &nonce, &sharedSecret) cipherTextB64 := base64.StdEncoding.EncodeToString(cipherText) return formatMessage(nonceB64, cipherTextB64) } -func formatMessage(nonce string, cipherText string) string { +func formatMessage(nonce string, cipherText string) (string, error) { encryptedMessage := &EncryptedMessage{ Nonce: nonce, Ciphertext: cipherText, } - json, err := json.Marshal(encryptedMessage) + jsonBytes, err := json.Marshal(encryptedMessage) if err != nil { - panic(err) + return "", fmt.Errorf("failed to marshal encrypted message: %w", err) } - - return string(json) + return string(jsonBytes), nil } -func generateNonce() [24]byte { +func generateNonce() ([24]byte, error) { var nonce [24]byte - //Trick ReadFull into thinking nonce is a slice if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { - panic(err) + return nonce, fmt.Errorf("failed to read random bytes: %w", err) } - return nonce + return nonce, nil } func generateSharedSecret(channel string, encryptionKey []byte) [32]byte { @@ -110,8 +112,7 @@ func decryptEvents(webhookData Webhook, encryptionKey []byte) (*Webhook, error) copy(nonce[:], []byte(nonceBytes[:])) sharedSecret := generateSharedSecret(event.Channel, encryptionKey) - box := []byte(cipherTextBytes) - decryptedBox, ok := secretbox.Open([]byte{}, box, &nonce, &sharedSecret) + decryptedBox, ok := secretbox.Open(nil, cipherTextBytes, &nonce, &sharedSecret) if !ok { return decryptedWebhooks, errors.New("Failed to decrypt event, possibly wrong key?") } diff --git a/crypto_test.go b/crypto_test.go index 6e54dff..32379a7 100644 --- a/crypto_test.go +++ b/crypto_test.go @@ -4,7 +4,7 @@ import ( "encoding/hex" "testing" - "gopkg.in/stretchr/testify.v1/assert" + "github.com/stretchr/testify/assert" ) func TestHmacSignature(t *testing.T) { @@ -74,7 +74,8 @@ func TestEncrypt(t *testing.T) { channel := "private-encrypted-bla" body := []byte("Hello!") encryptionKey := []byte("This is a string that is 32 chars") - cipherText := encrypt(channel, body, encryptionKey) + cipherText, err := encrypt(channel, body, encryptionKey) + assert.NoError(t, err) assert.NotNil(t, cipherText) assert.NotEqual(t, body, cipherText) } @@ -82,7 +83,8 @@ func TestEncrypt(t *testing.T) { func TestFormatMessage(t *testing.T) { nonce := "a" cipherText := "b" - formatted := formatMessage(nonce, cipherText) + formatted, err := formatMessage(nonce, cipherText) + assert.NoError(t, err) assert.Equal(t, `{"nonce":"a","ciphertext":"b"}`, formatted) } diff --git a/encoder.go b/encoder.go index edcb975..f51ff9d 100644 --- a/encoder.go +++ b/encoder.go @@ -2,7 +2,6 @@ package pusher import ( "encoding/json" - "errors" "fmt" ) @@ -35,7 +34,10 @@ func encodeTriggerBody( } var payloadData string if isEncryptedChannel(channels[0]) { - payloadData = encrypt(channels[0], dataBytes, encryptionKey) + payloadData, err = encrypt(channels[0], dataBytes, encryptionKey) + if err != nil { + return nil, err + } } else { payloadData = string(dataBytes) } @@ -47,7 +49,7 @@ func encodeTriggerBody( eventExceedsMaximumSize = len(payloadData) > overrideMaxMessagePayloadKB*1024 } if eventExceedsMaximumSize { - return nil, errors.New(fmt.Sprintf("Event payload exceeded maximum size (%d bytes is too much)", len(payloadData))) + return nil, fmt.Errorf("event payload exceeded maximum size (%d bytes is too much)", len(payloadData)) } eventPayload := map[string]interface{}{ "name": event, @@ -56,7 +58,7 @@ func encodeTriggerBody( } for k, v := range params { if _, ok := eventPayload[k]; ok { - return nil, errors.New(fmt.Sprintf("Paramater %s specified multiple times", k)) + return nil, fmt.Errorf("parameter %s specified multiple times", k) } eventPayload[k] = v } @@ -76,7 +78,10 @@ func encodeTriggerBatchBody( return nil, err } if isEncryptedChannel(e.Channel) { - stringifyedDataBytes = encrypt(e.Channel, dataBytes, encryptionKey) + stringifyedDataBytes, err = encrypt(e.Channel, dataBytes, encryptionKey) + if err != nil { + return nil, err + } } else { stringifyedDataBytes = string(dataBytes) } diff --git a/go.mod b/go.mod index 338aa98..d6d28b2 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,15 @@ -module github.com/pusher/pusher-http-go/v5 +module github.com/pusher/pusher-http-go/v6 -go 1.14 +go 1.26 require ( - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/stretchr/testify v1.11.1 + golang.org/x/crypto v0.49.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 - gopkg.in/stretchr/testify.v1 v1.2.2 + golang.org/x/sys v0.42.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c2bf946..25e7419 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,14 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M= -gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/request.go b/request.go index 2c9737a..7968aa9 100644 --- a/request.go +++ b/request.go @@ -2,11 +2,10 @@ package pusher import ( "bytes" - "errors" + "context" "fmt" - "io/ioutil" + "io" "net/http" - "strconv" ) const ( @@ -19,9 +18,11 @@ var headers = map[string]string{ "X-Pusher-Library": fmt.Sprintf("%s %s", libraryName, libraryVersion), } -// change timeout to time.Duration -func request(client *http.Client, method, url string, body []byte) ([]byte, error) { - req, err := http.NewRequest(method, url, bytes.NewBuffer(body)) +func request(ctx context.Context, client *http.Client, method, url string, body []byte) ([]byte, error) { + req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(body)) + if err != nil { + return nil, err + } for key, val := range headers { req.Header.Set(http.CanonicalHeaderKey(key), val) @@ -36,14 +37,12 @@ func request(client *http.Client, method, url string, body []byte) ([]byte, erro } func processResponse(response *http.Response) ([]byte, error) { - responseBody, err := ioutil.ReadAll(response.Body) + responseBody, err := io.ReadAll(response.Body) if err != nil { return nil, err } if response.StatusCode >= 200 && response.StatusCode < 300 { return responseBody, nil } - message := fmt.Sprintf("Status Code: %s - %s", strconv.Itoa(response.StatusCode), string(responseBody)) - err = errors.New(message) - return nil, err + return nil, fmt.Errorf("Status Code: %d - %s", response.StatusCode, responseBody) } diff --git a/request_url_test.go b/request_url_test.go index d4e8baf..24ec8c2 100644 --- a/request_url_test.go +++ b/request_url_test.go @@ -3,7 +3,7 @@ package pusher import ( "testing" - "gopkg.in/stretchr/testify.v1/assert" + "github.com/stretchr/testify/assert" ) func TestTriggerRequestUrl(t *testing.T) { diff --git a/response_parsing_test.go b/response_parsing_test.go index 5b97675..3a3b5be 100644 --- a/response_parsing_test.go +++ b/response_parsing_test.go @@ -3,7 +3,7 @@ package pusher import ( "testing" - "gopkg.in/stretchr/testify.v1/assert" + "github.com/stretchr/testify/assert" ) func TestParsingTriggerChannelsList(t *testing.T) { diff --git a/util.go b/util.go index 17adb5e..0395d4b 100644 --- a/util.go +++ b/util.go @@ -1,8 +1,8 @@ package pusher import ( - "errors" "encoding/json" + "errors" "fmt" "net/url" "regexp" @@ -59,10 +59,7 @@ func validUserId(userId string) bool { } func validChannel(channel string) bool { - if len(channel) > maxChannelNameSize || !channelValidationRegex.MatchString(channel) { - return false - } - return true + return len(channel) <= maxChannelNameSize && channelValidationRegex.MatchString(channel) } func channelsAreValid(channels []string) bool { @@ -75,10 +72,7 @@ func channelsAreValid(channels []string) bool { } func isEncryptedChannel(channel string) bool { - if strings.HasPrefix(channel, "private-encrypted-") { - return true - } - return false + return strings.HasPrefix(channel, "private-encrypted-") } func validateUserData(userData map[string]interface{}) (err error) { diff --git a/util_test.go b/util_test.go index 6b588c7..4c9dd42 100644 --- a/util_test.go +++ b/util_test.go @@ -3,7 +3,7 @@ package pusher import ( "testing" - "gopkg.in/stretchr/testify.v1/assert" + "github.com/stretchr/testify/assert" ) func TestParseUserAuthenticationRequestParamsNoSock(t *testing.T) { @@ -48,7 +48,7 @@ func TestInvalidChannelAuthorizationParams(t *testing.T) { func TestValidateUserDataSuccess(t *testing.T) { m := map[string]interface{}{ - "id": "12345", + "id": "12345", "email": "test@test.com", } err := validateUserData(m) @@ -65,7 +65,7 @@ func TestValidateUserDataNoId(t *testing.T) { func TestValidateUserDataIdIsNotString(t *testing.T) { m := map[string]interface{}{ - "id": 123, + "id": 123, "email": "test@test.com", } err := validateUserData(m) @@ -74,7 +74,7 @@ func TestValidateUserDataIdIsNotString(t *testing.T) { func TestValidateUserDataInvalidId(t *testing.T) { m := map[string]interface{}{ - "id": "", + "id": "", "email": "test@test.com", } err := validateUserData(m) diff --git a/webhook_test.go b/webhook_test.go index 60550a2..c17f616 100644 --- a/webhook_test.go +++ b/webhook_test.go @@ -4,7 +4,7 @@ import ( "net/http" "testing" - "gopkg.in/stretchr/testify.v1/assert" + "github.com/stretchr/testify/assert" ) func setUpClient() Client {