From 80ef6e881cbebc9a40e517309dfd342f0fcf7a7e Mon Sep 17 00:00:00 2001 From: Tiger Ren Date: Fri, 17 Jan 2025 01:45:51 +0800 Subject: [PATCH] Refactor sync process and enhance video handling for OneDrive integration - Updated 'start-sync' IPC event to process video files, specifically filtering for .mov and .mp4 formats. - Improved error handling and logging to provide feedback on video item retrieval and download status. - Integrated Graph API client for folder content retrieval prior to sync initiation. - Enhanced user feedback mechanisms for sync completion and error reporting. - Updated renderer.js to manage UI states during the sync process. --- renderer/AuthProvider.js | 140 +++++++++++++++++++++++++++++++++++++++ renderer/authConfig.js | 49 ++++++++++++++ renderer/constants.js | 16 +++++ renderer/graph.js | 22 ++++++ 4 files changed, 227 insertions(+) create mode 100644 renderer/AuthProvider.js create mode 100644 renderer/authConfig.js create mode 100644 renderer/constants.js create mode 100644 renderer/graph.js diff --git a/renderer/AuthProvider.js b/renderer/AuthProvider.js new file mode 100644 index 0000000..4fba94e --- /dev/null +++ b/renderer/AuthProvider.js @@ -0,0 +1,140 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +const { PublicClientApplication, InteractionRequiredAuthError } = require('@azure/msal-node'); +const { shell } = require('electron'); + +class AuthProvider { + msalConfig + clientApplication; + account; + cache; + + constructor(msalConfig) { + /** + * Initialize a public client application. For more information, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/initialize-public-client-application.md + */ + this.msalConfig = msalConfig; + this.clientApplication = new PublicClientApplication(this.msalConfig); + this.cache = this.clientApplication.getTokenCache(); + this.account = null; + } + + async login() { + const authResponse = await this.getToken({ + // If there are scopes that you would like users to consent up front, add them below + // by default, MSAL will add the OIDC scopes to every token request, so we omit those here + scopes: [], + }); + + return this.handleResponse(authResponse); + } + + async logout() { + if (!this.account) return; + + try { + /** + * If you would like to end the session with AAD, use the logout endpoint. You'll need to enable + * the optional token claim 'login_hint' for this to work as expected. For more information, visit: + * https://learn.microsoft.com/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request + */ + if (this.account.idTokenClaims.hasOwnProperty('login_hint')) { + await shell.openExternal(`${this.msalConfig.auth.authority}/oauth2/v2.0/logout?logout_hint=${encodeURIComponent(this.account.idTokenClaims.login_hint)}`); + } + + await this.cache.removeAccount(this.account); + this.account = null; + } catch (error) { + console.log(error); + } + } + + async getToken(tokenRequest) { + let authResponse; + const account = this.account || (await this.getAccount()); + + if (account) { + tokenRequest.account = account; + authResponse = await this.getTokenSilent(tokenRequest); + } else { + authResponse = await this.getTokenInteractive(tokenRequest); + } + + return authResponse || null; + } + + async getTokenSilent(tokenRequest) { + try { + return await this.clientApplication.acquireTokenSilent(tokenRequest); + } catch (error) { + if (error instanceof InteractionRequiredAuthError) { + console.log('Silent token acquisition failed, acquiring token interactive'); + return await this.getTokenInteractive(tokenRequest); + } + + console.log(error); + } + } + + async getTokenInteractive(tokenRequest) { + try { + const openBrowser = async (url) => { + await shell.openExternal(url); + }; + + const authResponse = await this.clientApplication.acquireTokenInteractive({ + ...tokenRequest, + openBrowser, + successTemplate: '

Successfully signed in!

You can close this window now.

', + errorTemplate: '

Oops! Something went wrong

Check the console for more information.

', + }); + + return authResponse; + } catch (error) { + throw error; + } + } + + /** + * Handles the response from a popup or redirect. If response is null, will check if we have any accounts and attempt to sign in. + * @param response + */ + async handleResponse(response) { + if (response !== null) { + this.account = response.account; + } else { + this.account = await this.getAccount(); + } + + return this.account; + } + + /** + * Calls getAllAccounts and determines the correct account to sign into, currently defaults to first account found in cache. + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md + */ + async getAccount() { + const currentAccounts = await this.cache.getAllAccounts(); + + if (!currentAccounts) { + console.log('No accounts detected'); + return null; + } + + if (currentAccounts.length > 1) { + // Add choose account code here + console.log('Multiple accounts detected, need to add choose account code.'); + return currentAccounts[0]; + } else if (currentAccounts.length === 1) { + return currentAccounts[0]; + } else { + return null; + } + } +} + +module.exports = AuthProvider; diff --git a/renderer/authConfig.js b/renderer/authConfig.js new file mode 100644 index 0000000..9019bd4 --- /dev/null +++ b/renderer/authConfig.js @@ -0,0 +1,49 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +const { LogLevel } = require("@azure/msal-node"); + +/** + * Configuration object to be passed to MSAL instance on creation. + * For a full list of MSAL.js configuration parameters, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/configuration.md + */ +const AAD_ENDPOINT_HOST = "https://login.microsoftonline.com/"; // include the trailing slash + +const msalConfig = { + auth: { + clientId: "d581ab07-3a21-44d3-84c4-16b06bef6266", + authority: `${AAD_ENDPOINT_HOST}common`, + }, + system: { + loggerOptions: { + loggerCallback(loglevel, message, containsPii) { + console.log(message); + }, + piiLoggingEnabled: false, + logLevel: LogLevel.Verbose, + }, + }, +}; + +/** + * Add here the endpoints and scopes when obtaining an access token for protected web APIs. For more information, see: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md + */ +const GRAPH_ENDPOINT_HOST = "https://graph.microsoft.com/"; // include the trailing slash + +const protectedResources = { + graphMe: { + endpoint: `${GRAPH_ENDPOINT_HOST}v1.0/me`, + // scopes: ["User.Read"], + scopes: ["OneDrive.ReadWrite", "offline_access", "openid", "profile", "User.Read"] + } +}; + + +module.exports = { + msalConfig: msalConfig, + protectedResources: protectedResources, +}; diff --git a/renderer/constants.js b/renderer/constants.js new file mode 100644 index 0000000..c27ba43 --- /dev/null +++ b/renderer/constants.js @@ -0,0 +1,16 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +const IPC_MESSAGES = { + SHOW_WELCOME_MESSAGE: 'SHOW_WELCOME_MESSAGE', + LOGIN: 'LOGIN', + LOGOUT: 'LOGOUT', + GET_PROFILE: 'GET_PROFILE', + SET_PROFILE: 'SET_PROFILE', +} + +module.exports = { + IPC_MESSAGES: IPC_MESSAGES, +} diff --git a/renderer/graph.js b/renderer/graph.js new file mode 100644 index 0000000..8bed213 --- /dev/null +++ b/renderer/graph.js @@ -0,0 +1,22 @@ +const { Client } = require('@microsoft/microsoft-graph-client'); +require('isomorphic-fetch'); + +/** + * Creating a Graph client instance via options method. For more information, visit: + * https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/docs/CreatingClientInstance.md#2-create-with-options + * @param {String} accessToken + * @returns + */ +const getGraphClient = (accessToken) => { + // Initialize Graph client + const graphClient = Client.init({ + // Use the provided access token to authenticate requests + authProvider: (done) => { + done(null, accessToken); + }, + }); + + return graphClient; +}; + +module.exports = getGraphClient; \ No newline at end of file