0

While testing my node app on Safari, routes that are stored in my browser's history seem to get pinged twice per request, and carry out all of the method calls associated with that path twice (the browser itself only seems to receive one response, though).

This is primarily an issue because I am interfacing with the Google Calendar API in order to add a new calendar event-- right now the test event gets added twice when I try to add it once. So far this only seems to happen in Safari (but I've only tested in Safari and Chrome).

What actually happens:

I begin typing "localhost:3000/add," the browser predicts the last two letters of the path, and as soon as the predictive text appears, I can see via console.log that the app executes the path defined at app.get('/add'), even though none of this activity is apparent on the browser end (e.g., the browser doesn't refresh and display the information it received from an API call, even though that info is being logged, and sending info to res is part of the same function causing the log statements).

Then, when I actually enter the address, the path executes again, but this time the browser refreshes upon receiving a response, and displays new data.

test.js:

"use strict";
const express = require('express');
const app = express();

//must be initialized with .init()
const CalendarManager = require('./GoogleCalApi.js');
const calendar = new CalendarManager();
require('dotenv').config('./.env');

calendar.init(process.env.GOOGLE_CLIENT_ID, 
    process.env.GOOGLE_CLIENT_SECRET, 
    process.env.REDIRECT_URL + '/oauthcallback', 
    (m) => console.log);

app.get('/', (req, res) => {
  res.sendFile(process.cwd() + '/views/test.html')
})

app.get('/verify', (req, res) => {
  calendar.getAuthUrl((err, url)=>{ err ? res.send('error getting auth url') : 
    res.redirect(url) });
})

app.get('/oauthcallback', (req, res) => {
    console.log('callback ping')
 let done = (err, message) => message ? res.send(message) : res.send(err);
 calendar.storeCred(req.query.code, done)
})

app.get('/add', (req, res) => {
    console.log('add  route ping')
    let event = {
        'summary': 'TestEvent',
        'start': {
          'date': '2019-07-13'
        },
        'end': {
          'date': '2019-07-14'
        }
    }
    calendar.addEvent(event, (err, data) => { res.send(err ? err : data) });
})

app.listen(3000, () => {console.log('listening on port 3000')})

GoogleCalApi.js:

const {google} = require('googleapis');


function GoogleCalendarApi () {
    this.initialized = false;
    this.oauth2Client = null;
    this.url = null;
    this.token = null;
    this.calendar = null;

    this.init = function (clientId, clientSecret, redirect_url, done) {
        this.oauth2Client = new google.auth.OAuth2(
            clientId,
            clientSecret, 
            redirect_url
        );
        this.scopes = ['https://www.googleapis.com/auth/calendar'];
        this.url = this.oauth2Client.generateAuthUrl({
              access_type: 'offline',
              scope: this.scopes
        })
        //listeners
        this.oauth2Client.on('tokens', (tokens) => {
            console.log('token event:', this.token)
          if (tokens.refresh_token) {
            this.token = tokens.refresh_token;
            console.log('refresh: ' + tokens.refresh_token)
          }
          console.log('auth token: ' + tokens.access_token)
        })
        console.log('initialized', this.oauth2Client)
        this.initialized = true;
        done('initialized');
    };
    /**
    * @param done: callback function that can handle two params: error and url
    *              -error should be null, or an error message
    *              -url should be an address for Google Cloud's authentication process
    **/
    this.getAuthUrl = (done) => { this.url ? done(null, this.url) : done('error: url not defined') }
    this.storeCred = function (code, done) { //code from req.query.code
        async function cred (auth) {
            try {
            console.log('storeCred ping')
              const {tokens} = await auth.getToken(code)
              auth.setCredentials(tokens)
              console.log('tokens', tokens)
              done(null, 'authenticated')
              } catch (err) {
                console.log('error in cred:', err)
                      done(err)
              }
        }
        try { cred(this.oauth2Client) } catch (err) { done(err) }

    }
    this.addEvent = function (event, done) {
        console.log('add event ping')
        if (this.calendar === null) this.calendar = google.calendar({version: 'v3', auth: this.oauth2Client});
        this.calendar.events.insert({
            auth: this.oauth2Client,
            calendarId: 'primary',
            resource: event
        }, (err, data) => { done(null, data) })
    }

} //end constructor

module.exports = GoogleCalendarApi;

As I described above, I'm seeing:

add  route ping  
add event ping  
add  route ping  
add event ping

in the console, when I should only be seeing:

add  route ping  
add event ping

And the google calendar I'm updating has a duplicate event when I visit the calendar page itself.

(Also, I'm pretty unfamiliar with using markdown, so if formatting for the code portion of this question fails, I apologize in advance, and please feel free to ignore the post until I'm able to fix it)

esopp
  • 26
  • 5

1 Answers1

0

This issue is indeed caused by Safari automatically preloading suggested urls. As per one of krispy's comments in this discussion, the web standard is simply not to perform actions as the result of a GET request, but to use POST, PUT, or DELETE instead, as none of these types of requests result in preloading.

esopp
  • 26
  • 5