Skip to content

Commit

Permalink
Merge pull request #3 from pusher/use-api-v1
Browse files Browse the repository at this point in the history
Implement client for FCM API V1
  • Loading branch information
fbenevides authored Mar 4, 2024
2 parents 25559b3 + daed235 commit 5a3bb7a
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.DS_Store
main/
96 changes: 96 additions & 0 deletions v1/fcm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package googlemessaging

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"

"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
)

const (
BaseURL = "https://fcm.googleapis.com/v1"
FirebaseRequestScope = "https://www.googleapis.com/auth/firebase.messaging"
DefaultContentType = "application/json"
)

type ServiceAccount struct {
ServiceAccountType string `json:"type"`
ProjectId string `json:"project_id"`
PrivateKeyId string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
ClientEmail string `json:"client_email"`
ClientId string `json:"client_id"`
AuthUri string `json:"auth_uri"`
TokenUri string `json:"token_uri"`
}

type fcmClient struct {
httpClient *http.Client
ServiceAccountConfig *ServiceAccount
}

func getServiceAccountFromContent(content string) (*ServiceAccount, error) {
serviceAccount := &ServiceAccount{}
err := json.Unmarshal([]byte(content), &serviceAccount)
if err != nil {
return nil, fmt.Errorf("error umarshalling service account file>%v", err)
}

return serviceAccount, nil
}

func NewFcmClient(serviceAccountFileContent string, ctx context.Context) (*fcmClient, error) {
serviceAccountConfig, err := getServiceAccountFromContent(serviceAccountFileContent)
if err != nil {
return nil, err
}

conf := &jwt.Config{
Email: serviceAccountConfig.ClientEmail,
PrivateKeyID: serviceAccountConfig.PrivateKeyId,
PrivateKey: []byte(serviceAccountConfig.PrivateKey),
Scopes: []string{FirebaseRequestScope},
TokenURL: google.JWTTokenURL,
}

return &fcmClient{
httpClient: conf.Client(ctx),
ServiceAccountConfig: serviceAccountConfig,
}, nil
}

func (c *fcmClient) Send(m FcmMessageBody) (*FcmSendHttpResponse, error) {
sendURL := fmt.Sprintf("%s/projects/%s/messages:send",
BaseURL, c.ServiceAccountConfig.ProjectId,
)

body, err := json.Marshal(m)
if err != nil {
return nil, fmt.Errorf("error unmarshaling message>%v", err)
}

resp, err := c.httpClient.Post(sendURL, DefaultContentType, bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("error sending request to HTTP connection server>%v", err)
}

responseBody, _ := io.ReadAll(resp.Body)
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("could not send a message as the server returned: %s", resp.StatusCode)
}

fcmResp := &FcmSendHttpResponse{}
err = json.Unmarshal(responseBody, &fcmResp)
if err != nil {
return nil, fmt.Errorf("error unmarshaling json from body: %v", err)
}

return fcmResp, nil
}
41 changes: 41 additions & 0 deletions v1/fcm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package googlemessaging

import (
"fmt"
"os"
"testing"
)

const (
ServiceAccountFilePath = "fixtures/service_account.json"
)

func assertEqual(t *testing.T, v, e interface{}) {
if v != e {
t.Fatalf("%#v != %#v", v, e)
}
}

func readFixture(path string) string {
data, err := os.ReadFile(path)
if err != nil {
panic(fmt.Sprintf("error while reading %v", path))
}

return string(data)
}

func TestGetValidServiceAccount(t *testing.T) {
serviceAccountFileContent := readFixture(ServiceAccountFilePath)

acc, err := getServiceAccountFromContent(serviceAccountFileContent)

assertEqual(t, err, nil)
assertEqual(t, acc.ProjectId, "pusher-project-id")
assertEqual(t, acc.PrivateKeyId, "12345")
assertEqual(t, acc.PrivateKey, "-----BEGIN PRIVATE KEY-----\nprivate_key\n-----END PRIVATE KEY-----\n")
assertEqual(t, acc.ClientEmail, "firebase-adminsdk-g680q@pusher-project-id.iam.gserviceaccount.com")
assertEqual(t, acc.ClientId, "12345")
assertEqual(t, acc.AuthUri, "https://accounts.google.com/o/oauth2/auth")
assertEqual(t, acc.TokenUri, "https://oauth2.googleapis.com/token")
}
13 changes: 13 additions & 0 deletions v1/fixtures/service_account.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "pusher-project-id",
"private_key_id": "12345",
"private_key": "-----BEGIN PRIVATE KEY-----\nprivate_key\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-g680q@pusher-project-id.iam.gserviceaccount.com",
"client_id": "12345",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-g680q@pusher-project-id.iam.gserviceaccount.comq%40test-flutter-beams-sdk.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
44 changes: 44 additions & 0 deletions v1/requests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package googlemessaging

type FcmMessageBody struct {
Message *FcmHttpMessage `json:"message"`
}

type FcmHttpMessage struct {
Token string `json:"token,omitempty"`
Notification *FcmNotification `json:"notification,omitempty"`
Data FcmData `json:"data,omitempty"`
Android *FcmAndroidConfig `json:"android"`
}

type FcmNotification struct {
Title string `json:"title"`
Body string `json:"body"`
Image string `json:"image"`
}

type FcmAndroidConfig struct {
Notification *FcmAndroidNotification `json:"notification"`
}

type FcmAndroidNotification struct {
Icon string `json:"icon,omitempty"`
Color string `json:"color,omitempty"`
Sound string `json:"sound,omitempty"`
Tag string `json:"tag,omitempty"`
ClickAction string `json:"click_action,omitempty"`
BodyLocKey string `json:"body_loc_key,omitempty"`
BodyLocArgs []string `json:"body_loc_args,omitempty"`
TitleLocKey string `json:"title_loc_key,omitempty"`
TitleLocArgs []string `json:"title_loc_args,omitempty"`
Ticker string `json:"ticker,omitempty"`
Sticky bool `json:"sticky,omitempty"`
LocalOnly bool `json:"local_only,omitempty"`
NotificationCount int `json:"notification_count,omitempty"`
}

type FcmData map[string]map[string]interface{}

type FcmSendHttpResponse struct {
Name string `json:"name"`
}

0 comments on commit 5a3bb7a

Please sign in to comment.