diff --git a/deprecated.go b/deprecated.go new file mode 100644 index 0000000..efe6774 --- /dev/null +++ b/deprecated.go @@ -0,0 +1,175 @@ +package fcm + +/* +const ( + // Batch Send API + BatchSendEndpoint = "https://fcm.googleapis.com/batch" + // Requests to the endpoint will start failing after 6/21/2024. + // Migrate to the standard HTTP v1 API send method, which supports HTTP/2 for multiplexing. +) + +type ClientConfig struct { + SendEndpoint string + //BatchSendEndpoint string +} + +// Deprecated: Use SendEach instead. +func (c *Client) SendMessages(messages []Message) (MultiSendResponse, error) { + return c.doSendMessages(messages, false) +} + +func (c *Client) ValidateMessages(messages []Message) (MultiSendResponse, error) { + return c.doSendMessages(messages, true) +} + +func (c *Client) doSendMessages(messages []Message, validateOnly bool) (MultiSendResponse, error) { + messageCount := len(messages) + if messageCount == 0 { + return MultiSendResponse{}, nil + } + if messageCount > maxMessages { + return MultiSendResponse{}, errors.New(fmt.Sprintf("messages limit (%d) exceeded: %d", maxMessages, messageCount)) + } + + accessToken, err := c.ts.Token() + if err != nil { + return MultiSendResponse{}, err + } + + var body bytes.Buffer + w := multipart.NewWriter(&body) + w.SetBoundary(multipartBoundary) + + for index, msg := range messages { + req := SendRequest{ + ValidateOnly: validateOnly, + Message: msg, + } + + body, err := c.makeMessageRequest(req) + if err != nil { + return MultiSendResponse{}, err + } + + if err := writePartTo(w, body, index); err != nil { + return MultiSendResponse{}, err + } + } + + if err := w.Close(); err != nil { + return MultiSendResponse{}, errors.WithStack(err) + } + + request, err := http.NewRequest(http.MethodPost, c.cfg.BatchSendEndpoint, &body) + if err != nil { + return MultiSendResponse{}, errors.WithStack(err) + } + + accessToken.SetAuthHeader(request) + request.Header.Set("Content-Type", fmt.Sprintf(`multipart/mixed; boundary="%s"`, multipartBoundary)) + + response, err := c.hc.Do(request) + if err != nil { + return MultiSendResponse{}, errors.WithStack(err) + } + defer response.Body.Close() + + return c.makeMultiSendResponse(response, messageCount) +} + +func (c *Client) makeMessageRequest(req SendRequest) ([]byte, error) { + reqJson, err := json.Marshal(req) + if err != nil { + return nil, errors.WithStack(err) + } + + request, err := http.NewRequest(http.MethodPost, c.cfg.SendEndpoint, bytes.NewBuffer(reqJson)) + if err != nil { + return nil, errors.WithStack(err) + } + + request.Header.Set("Content-Type", "application/json; charset=UTF-8") + request.Header.Set("User-Agent", "") + + var body bytes.Buffer + if err := request.Write(&body); err != nil { + return nil, errors.WithStack(err) + } + + return body.Bytes(), nil +} + +func writePartTo(w *multipart.Writer, bytes []byte, index int) error { + header := make(textproto.MIMEHeader) + header.Set("Content-Type", "application/http") + header.Set("Content-Transfer-Encoding", "binary") + header.Set("Content-ID", fmt.Sprintf("%d", index+1)) + + part, err := w.CreatePart(header) + if err != nil { + return errors.WithStack(err) + } + if _, err := part.Write(bytes); err != nil { + return errors.WithStack(err) + } + + return nil +} + +func (c *Client) makeMultiSendResponse(response *http.Response, totalCount int) (MultiSendResponse, error) { + responses := make([]SendResponse, 0, totalCount) + var fails int + + _, params, err := mime.ParseMediaType(response.Header.Get("Content-Type")) + if err != nil { + return MultiSendResponse{}, errors.WithStack(err) + } + + reader := multipart.NewReader(response.Body, params["boundary"]) + for { + part, err := reader.NextPart() + if err == io.EOF { + break + } else if err != nil { + return MultiSendResponse{}, errors.WithStack(err) + } + + resp, err := makeSendResponseFromPart(part) + if err != nil { + return MultiSendResponse{}, err + } + + responses = append(responses, resp) + if resp.HasError() { + c.logger.Info("fail", "error", fmt.Sprintf("%+v", *resp.Error)) + fails++ + } + } + + return MultiSendResponse{ + Responses: responses, + Sent: totalCount - fails, + Failed: fails, + }, nil +} + +func makeSendResponseFromPart(part *multipart.Part) (SendResponse, error) { + response, err := http.ReadResponse(bufio.NewReader(part), nil) + if err != nil { + return SendResponse{}, errors.WithStack(err) + } + defer response.Body.Close() + + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return SendResponse{}, errors.WithStack(err) + } + + var resp SendResponse + if err := json.Unmarshal(body, &resp); err != nil { + return SendResponse{}, errors.WithMessagef(err, "response body: %s", body) + } + + return resp, nil +} +*/ diff --git a/fcm.go b/fcm.go index 86c89ef..4bbb983 100644 --- a/fcm.go +++ b/fcm.go @@ -10,17 +10,13 @@ package fcm import ( - "bufio" "bytes" "context" "encoding/json" "fmt" "io" "io/ioutil" - "mime" - "mime/multipart" "net/http" - "net/textproto" "sync" "git.bit5.ru/backend/errors" @@ -43,11 +39,6 @@ const ( maxMessages = 500 multipartBoundary = "msg_boundary" - - // Batch Send API - BatchSendEndpoint = "https://fcm.googleapis.com/batch" - // Requests to the endpoint will start failing after 6/21/2024. - // Migrate to the standard HTTP v1 API send method, which supports HTTP/2 for multiplexing. ) var ( @@ -87,8 +78,7 @@ func ReadCredentialsFromFile(filename string) (Credentials, error) { } type ClientConfig struct { - SendEndpoint string - BatchSendEndpoint string + SendEndpoint string } type Client struct { @@ -248,166 +238,6 @@ func (c *Client) doSendRequest(ctx context.Context, req SendRequest, loggerEnabl return resp, nil } -// Deprecated: Use SendEach instead. -func (c *Client) SendMessages(messages []Message) (MultiSendResponse, error) { - return c.doSendMessages(messages, false) -} - -func (c *Client) ValidateMessages(messages []Message) (MultiSendResponse, error) { - return c.doSendMessages(messages, true) -} - -func (c *Client) doSendMessages(messages []Message, validateOnly bool) (MultiSendResponse, error) { - messageCount := len(messages) - if messageCount == 0 { - return MultiSendResponse{}, nil - } - if messageCount > maxMessages { - return MultiSendResponse{}, errors.New(fmt.Sprintf("messages limit (%d) exceeded: %d", maxMessages, messageCount)) - } - - accessToken, err := c.ts.Token() - if err != nil { - return MultiSendResponse{}, err - } - - var body bytes.Buffer - w := multipart.NewWriter(&body) - w.SetBoundary(multipartBoundary) - - for index, msg := range messages { - req := SendRequest{ - ValidateOnly: validateOnly, - Message: msg, - } - - body, err := c.makeMessageRequest(req) - if err != nil { - return MultiSendResponse{}, err - } - - if err := writePartTo(w, body, index); err != nil { - return MultiSendResponse{}, err - } - } - - if err := w.Close(); err != nil { - return MultiSendResponse{}, errors.WithStack(err) - } - - request, err := http.NewRequest(http.MethodPost, c.cfg.BatchSendEndpoint, &body) - if err != nil { - return MultiSendResponse{}, errors.WithStack(err) - } - - accessToken.SetAuthHeader(request) - request.Header.Set("Content-Type", fmt.Sprintf(`multipart/mixed; boundary="%s"`, multipartBoundary)) - - response, err := c.hc.Do(request) - if err != nil { - return MultiSendResponse{}, errors.WithStack(err) - } - defer response.Body.Close() - - return c.makeMultiSendResponse(response, messageCount) -} - -func (c *Client) makeMessageRequest(req SendRequest) ([]byte, error) { - reqJson, err := json.Marshal(req) - if err != nil { - return nil, errors.WithStack(err) - } - - request, err := http.NewRequest(http.MethodPost, c.cfg.SendEndpoint, bytes.NewBuffer(reqJson)) - if err != nil { - return nil, errors.WithStack(err) - } - - request.Header.Set("Content-Type", "application/json; charset=UTF-8") - request.Header.Set("User-Agent", "") - - var body bytes.Buffer - if err := request.Write(&body); err != nil { - return nil, errors.WithStack(err) - } - - return body.Bytes(), nil -} - -func writePartTo(w *multipart.Writer, bytes []byte, index int) error { - header := make(textproto.MIMEHeader) - header.Set("Content-Type", "application/http") - header.Set("Content-Transfer-Encoding", "binary") - header.Set("Content-ID", fmt.Sprintf("%d", index+1)) - - part, err := w.CreatePart(header) - if err != nil { - return errors.WithStack(err) - } - if _, err := part.Write(bytes); err != nil { - return errors.WithStack(err) - } - - return nil -} - -func (c *Client) makeMultiSendResponse(response *http.Response, totalCount int) (MultiSendResponse, error) { - responses := make([]SendResponse, 0, totalCount) - var fails int - - _, params, err := mime.ParseMediaType(response.Header.Get("Content-Type")) - if err != nil { - return MultiSendResponse{}, errors.WithStack(err) - } - - reader := multipart.NewReader(response.Body, params["boundary"]) - for { - part, err := reader.NextPart() - if err == io.EOF { - break - } else if err != nil { - return MultiSendResponse{}, errors.WithStack(err) - } - - resp, err := makeSendResponseFromPart(part) - if err != nil { - return MultiSendResponse{}, err - } - - responses = append(responses, resp) - if resp.HasError() { - c.logger.Info("fail", "error", fmt.Sprintf("%+v", *resp.Error)) - fails++ - } - } - - return MultiSendResponse{ - Responses: responses, - Sent: totalCount - fails, - Failed: fails, - }, nil -} - -func makeSendResponseFromPart(part *multipart.Part) (SendResponse, error) { - response, err := http.ReadResponse(bufio.NewReader(part), nil) - if err != nil { - return SendResponse{}, errors.WithStack(err) - } - defer response.Body.Close() - - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return SendResponse{}, errors.WithStack(err) - } - - var resp SendResponse - if err := json.Unmarshal(body, &resp); err != nil { - return SendResponse{}, errors.WithMessagef(err, "response body: %s", body) - } - - return resp, nil -} - func (c *Client) SendEach(ctx context.Context, messages []Message) (MessageMultiSendResponse, error) { ctx, span := tracer.Start(ctx, "Client.SendEach")