diff --git a/README.md b/README.md index 6f3cc40..cb5dd18 100755 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ After these initial setup steps, you're ready to create an OAuth 2.0 client ID: 6. **Authenticate with Google:** - Inside the MMM-GoogleCalendar directory, run `node authorize.js` from your terminal. - - This command will open a Google sign-in page in your web browser. Log in with your Google account as you normally would. + - This command will open a Google sign-in page in your web browser. Log in with your Google account as you normally would. If your browser does not open, use the link from the output in your terminal. - During this process, you might see a screen alerting you that "Google hasn't verified this app." This is a standard message for apps using OAuth that aren't published yet. Simply look for and click on the "Continue" button to proceed with the authentication. By completing these steps, you've successfully laid the groundwork for your Google Calendar to communicate with your MagicMirror. The module is installed, and with the necessary permissions configured, you're ready to personalize your calendar settings. diff --git a/authorize.js b/authorize.js index 1df753e..1d21011 100644 --- a/authorize.js +++ b/authorize.js @@ -1,94 +1,110 @@ -const fs = require("fs").promises; -const path = require("path"); -const process = require("process"); -const { authenticate } = require("@google-cloud/local-auth"); -const { google } = require("googleapis"); +const fs = require('fs'); +const readline = require('readline'); +const { google } = require('googleapis'); +const http = require('http'); +const destroyer = require('server-destroy'); -// If modifying these scopes, delete token.json. -const SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]; -// The file token.json stores the user's access and refresh tokens, and is -// created automatically when the authorization flow completes for the first -// time. -const TOKEN_PATH = path.join(process.cwd(), "token.json"); -const CREDENTIALS_PATH = path.join(process.cwd(), "credentials.json"); +console.log('Starting authorization process...'); -/** - * Reads previously authorized credentials from the save file. - * - * @return {Promise} - */ -async function loadSavedCredentialsIfExist() { - try { - const content = await fs.readFile(TOKEN_PATH); - const credentials = JSON.parse(content); - return google.auth.fromJSON(credentials); - } catch (err) { - console.error("MMM-GoogleCalendar: Error loading credentials", err); - return null; +const SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']; +const TOKEN_PATH = 'token.json'; + +process.on('uncaughtException', (err) => { + console.error('Uncaught Exception:', err); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled Rejection at:', promise, 'reason:', reason); +}); + +// Load client secrets from a local file. +fs.readFile('credentials.json', (err, content) => { + if (err) { + console.log('Error loading client secret file:', err); + return; } -} + console.log('Successfully loaded credentials.json'); + authorize(JSON.parse(content), listEvents); +}); -/** - * Serializes credentials to a file compatible with GoogleAUth.fromJSON. - * - * @param {OAuth2Client} client - * @return {Promise} - */ -async function saveCredentials(client) { - const content = await fs.readFile(CREDENTIALS_PATH); - const keys = JSON.parse(content); - const key = keys.installed || keys.web; - const payload = JSON.stringify({ - type: "authorized_user", - client_id: key.client_id, - client_secret: key.client_secret, - refresh_token: client.credentials.refresh_token +// Authorize function +function authorize(credentials, callback) { + console.log('Authorizing with provided credentials...'); + const { client_secret, client_id, redirect_uris = [] } = credentials.installed || credentials.web || {}; + const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]); + + console.log('Checking for existing token...'); + fs.readFile(TOKEN_PATH, (err, token) => { + if (err) { + console.log('No existing token found, requesting new access token...'); + return getAccessToken(oAuth2Client, callback, redirect_uris); // Fetch new token if not available + } + console.log('Token found, proceeding...'); + oAuth2Client.setCredentials(JSON.parse(token)); + callback(oAuth2Client); }); - await fs.writeFile(TOKEN_PATH, payload); } -/** - * Load or request or authorization to call APIs. - * - */ -async function authorize() { - let client = await loadSavedCredentialsIfExist(); - if (client) { - return client; - } - client = await authenticate({ - scopes: SCOPES, - keyfilePath: CREDENTIALS_PATH +// Get Access Token function +function getAccessToken(oAuth2Client, callback, redirect_uris) { + const authUrl = oAuth2Client.generateAuthUrl({ + access_type: 'offline', + scope: SCOPES, }); - if (client.credentials) { - await saveCredentials(client); - } - return client; -} -/** - * Lists the next 10 events on the user's primary calendar. - * @param {google.auth.OAuth2} auth An authorized OAuth2 client. - */ -async function listEvents(auth) { - const calendar = google.calendar({ version: "v3", auth }); - const res = await calendar.events.list({ - calendarId: "primary", - timeMin: new Date().toISOString(), - maxResults: 10, - singleEvents: true, - orderBy: "startTime" + console.log('Generated auth URL:', authUrl); + + // Desktop app flow (localhost) + const server = http.createServer(async (req, res) => { + console.log(`Incoming request: ${req.method} ${req.url}`); // Log incoming requests + try { + if (req.url.startsWith('/')) { // Accept any path + console.log(`Handling callback for: ${req.url}`); + const qs = new URL(req.url, `http://localhost:${req.socket.localPort}`).searchParams; + res.end('Authentication successful! Please return to the console.'); + server.destroy(); + const { tokens } = await oAuth2Client.getToken(qs.get('code')); + oAuth2Client.setCredentials(tokens); + fs.writeFile(TOKEN_PATH, JSON.stringify(tokens), (err) => { + if (err) console.error(err); + console.log('Token stored to', TOKEN_PATH); + }); + callback(oAuth2Client); + } + } catch (e) { + console.error(e); + } }); - const events = res.data.items; - if (!events || events.length === 0) { - console.log("MMM-GoogleCalendar: No upcoming events found."); - return; - } - console.log("MMM-GoogleCalendar: Upcoming 10 events:"); - events.map((event, i) => { - const start = event.start.dateTime || event.start.date; - console.log(`${start} - ${event.summary}`); + + server.on('error', (err) => { + console.error('Server error:', err); }); + + server.listen(80, () => { + console.log('Server listening at http://localhost'); + console.log('Opening browser for authorization:', authUrl); + import('open').then((open) => { + open.default(authUrl); + console.log(`If the browser does not open, visit this URL: ${authUrl}`); + }); + }); + destroyer(server); } -authorize().then(listEvents).catch(console.error); +// List events to confirm authorization +function listEvents(auth) { + const calendar = google.calendar({ version: 'v3', auth }); + calendar.calendarList.list({}, (err, res) => { + if (err) { + console.log('The API returned an error: ' + err); + return; + } + const calendars = res.data.items; + if (calendars.length) { + console.log('Your calendars:'); + calendars.forEach((cal) => console.log(`- ${cal.summary} (ID: ${cal.id})`)); + } else { + console.log('No calendars found.'); + } + }); +} diff --git a/package.json b/package.json index b3690a8..ce9f230 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mmm-googlecalendar", - "version": "1.1.4", + "version": "1.1.5", "displayName": "MMM-GoogleCalendar", "description": "Display your Google calendars in MagicMirror (including google's family calendar) without using iCals nor any public calendar. ", "main": "MMM-GoogleCalendar.js", @@ -27,6 +27,9 @@ "homepage": "https://github.com/randomBrainstormer/MMM-GoogleCalendar#readme", "dependencies": { "@google-cloud/local-auth": "^2.1.0", - "googleapis": "^105.0.0" + "googleapis": "^105.0.0", + "open": "^10.1.0", + "readline": "^1.3.0", + "server-destroy": "^1.0.1" } }