Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question(Calendar API) : Getting to many push notifications, Is there a way to receive them only once? #20532

Open
YongGoose opened this issue Apr 19, 2024 · 2 comments
Labels
api: calendar Issues related to the Calendar API API. priority: p2 Moderately-important priority. Fix may not be included in next release. type: question Request for information or clarification. Not an issue.

Comments

@YongGoose
Copy link

YongGoose commented Apr 19, 2024

Hi I am using the watch to receive push notifications when events change.

I have successfully created a channel.

{
  "expiration": 1714380331000,
  "id": "<id>",
  "kind": "api#channel",
  "resourceId": "<resourceId>",
  "resourceUri": "https://www.googleapis.com/calendar/v3/calendars/primary/events?alt=json",
  "token": "tokenValue"
}

..and I received a push notification with the following code:

    @PostMapping("/notifications")
    public ResponseEntity<List<GoogleCalendarEventResponse>> printNotification(@RequestHeader(WebhookHeaders.RESOURCE_ID) String resourceId,
                                                                               @RequestHeader(WebhookHeaders.RESOURCE_URI) String resourceUri,
                                                                               @RequestHeader(WebhookHeaders.CHANNEL_ID) String channelId,
                                                                               @RequestHeader(WebhookHeaders.CHANNEL_EXPIRATION) String channelExpiration,
                                                                               @RequestHeader(WebhookHeaders.RESOURCE_STATE) String resourceState,
                                                                               @RequestHeader(WebhookHeaders.MESSAGE_NUMBER) String messageNumber) {
        googleService.listEvents(channelId);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

I've successfully managed to receive push notifications and store changed events in the database, but I'm encountering an issue of receiving too many push notifications.
image

Am I doing something wrong? Or if this issue is a bug, I'd like to contribute.


using library : com.google.apis:google-api-services-calendar:v3-rev20231123-2.0.0

@diegomarquezp diegomarquezp added type: question Request for information or clarification. Not an issue. priority: p2 Moderately-important priority. Fix may not be included in next release. labels Apr 19, 2024
@diegomarquezp
Copy link
Contributor

Hi @YongGoose, thanks for reporting this. From what I understand you have an application that launches a request to Calendar.Events.Watch. However the log shows many POST requests to this endpoint occurring, which makes me suspect that we are getting a similar set of notifications after each POST.
Would you mind sharing a bit on your use case, hoping to understand why this POST occurs many times? I'm just trying to narrow down this (maybe it's the suspected repeated set of notifications what worries us).

Anyhow, a small reproducible example (or snippet) would be of great help troubleshoot this. Would you mind sharing one if your time allows?

@diegomarquezp diegomarquezp added the api: calendar Issues related to the Calendar API API. label Apr 19, 2024
@YongGoose
Copy link
Author

YongGoose commented Apr 19, 2024

@diegomarquezp Of course!! Thank you for kindly answering my question.


The process of creating a channel.

After signing in through the servlet, it redirects to the specified callback URL. /oauth2Callback

@WebServlet(urlPatterns = "/oauth2Login")
public class GoogleAuthenticationServlet extends AbstractAuthorizationCodeServlet {
    @Override
    protected AuthorizationCodeFlow initializeFlow() {
        return GoogleUtils.initializeFlow();
    }

    @Override
    protected String getRedirectUri(HttpServletRequest httpServletRequest) {
        return GoogleUtils.getRedirectUri(httpServletRequest);
    }

    @Override
    protected String getUserId(HttpServletRequest httpServletRequest) {
        return GoogleUtils.getClientId(httpServletRequest);
    }
}

It calls the /google/watch URL with the accessToken and refreshToken, which are necessary pieces of information for issuing a channel, stored in cookies.

@WebServlet(urlPatterns = "/oauth2callback")
public class Oauth2CallbackServlet extends AbstractAuthorizationCodeCallbackServlet {

    @Override
    protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
            throws IOException {
        String userId = req.getSession().getId();
        ResponseCookie accessTokenCookie = ResponseCookie.from(ACCESS_TOKEN, credential.getAccessToken())
                .httpOnly(true)
                .build();
        ResponseCookie refreshTokenCookie = ResponseCookie.from(REFRESH_TOKEN, credential.getRefreshToken())
                .httpOnly(true)
                .build();
        resp.addHeader(HttpHeaders.SET_COOKIE, accessTokenCookie.toString());
        resp.addHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString());
        resp.sendRedirect("/google/watch?userId=" + userId);
    }

      ...

After receiving the necessary information from the callback servlet, it creates a channel.


//@RestController + @RequiredMapping
@RestControllerWithMapping("/google")
@RequiredArgsConstructor
public class GoogleCalendarControllerImpl implements GoogleCalendarController {
    private final GoogleService googleService;

      ...

    @GetMapping("/watch")
    public ResponseEntity<Channel> watchCalendar(@RequestParam String userId,
                                                 HttpServletRequest request) {
        TokenResponse tokenResponse = createTokenResponse(request.getCookies());
        String accessToken = AUTHORIZATION_BEARER + tokenResponse.getAccessToken();
        HttpHeaders headers = new HttpHeaders();
        headers.add(AUTHORIZATION, accessToken);

        Channel channel = googleService.executeWatchRequest(userId);
        googleService.registGoogleCredentials(channel, tokenResponse, userId);
        return ResponseEntity.ok()
                .headers(headers)
                .body(channel);
    }

      ...

create a channel with credential & watch

    /*

    The channel object used here is initialized through the @PostConstruct annotation,
    with the address set to "/google/notifications".

    CALENDAR_ID -> primary
     */

    public Channel executeWatchRequest(String userId) {
        try {
            Calendar.Events.Watch watch = calendarService(userId)
                    .events()
                    .watch(CALENDAR_ID, channel); 
            return watch.execute();
        } catch (IOException e) {
            throw new GoogleCalendarWatchException();
        }
    }

    public Calendar calendarService(String userId) {
        Credential credential = getCredentials(userId);
        return new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
                .setApplicationName(APPLICATION_NAME)
                .build();
    }

    public static Credential getCredentials(String userId) {
        try {
            return flow.loadCredential(userId);
        } catch (IOException e) {
            throw new GoogleCredentialException();
        }
    }

After changes occur in the events, it receives push notifications at "google/notifications", which is the channel's address.

//@RestController + @RequiredMapping
@RestControllerWithMapping("/google")
@RequiredArgsConstructor
public class GoogleCalendarControllerImpl implements GoogleCalendarController {
    private final GoogleService googleService;

    @PostMapping("/notifications")
    public ResponseEntity<List<GoogleCalendarEventResponse>> printNotification(@RequestHeader(WebhookHeaders.RESOURCE_ID) String resourceId,
                                                                               @RequestHeader(WebhookHeaders.RESOURCE_URI) String resourceUri,
                                                                               @RequestHeader(WebhookHeaders.CHANNEL_ID) String channelId,
                                                                               @RequestHeader(WebhookHeaders.CHANNEL_EXPIRATION) String channelExpiration,
                                                                               @RequestHeader(WebhookHeaders.RESOURCE_STATE) String resourceState,
                                                                               @RequestHeader(WebhookHeaders.MESSAGE_NUMBER) String messageNumber) {
        googleService.listEvents(channelId);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }


In the following scenario, multiple push notifications can be received:

create an event

image

This is the received push notification after the event is generated. I checked the logs via AWS CloudWatch.
http://colorscripter.com/s/w9djP1f
I uploaded the log to Color Scripter due to its length and shared the link.

Upon checking the database, I confirmed that the data was successfully stored.
image


If you need more information, feel free to let me know in the comments!
wish you have a gread day🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: calendar Issues related to the Calendar API API. priority: p2 Moderately-important priority. Fix may not be included in next release. type: question Request for information or clarification. Not an issue.
Projects
None yet
Development

No branches or pull requests

2 participants