Skip to content

Commit

Permalink
feat: add ElectronContextBridgeProvider (#2914)
Browse files Browse the repository at this point in the history
* feat: add ElectronContextBridgeProvider

* fix: missing export for ElectronContextBridgeProvider

---------

Co-authored-by: Gavin Barron <[email protected]>
  • Loading branch information
trulysinclair and gavinbarron authored Jan 3, 2024
1 parent ebf5ed3 commit 8900eb4
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 1 deletion.
83 changes: 82 additions & 1 deletion packages/providers/mgt-electron-provider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The [Microsoft Graph Toolkit (mgt)](https://aka.ms/mgt) library is a collection
The `@microsoft/mgt-electron-provider` package exposes the `ElectronAuthenticator` and `ElectronProvider` classes which use [MSAL node](https://www.npmjs.com/package/@azure/msal-node) to sign in users and acquire tokens to use with Microsoft Graph.


## Usage
## Usage without Context Bridge

1. Install the packages

Expand Down Expand Up @@ -48,6 +48,87 @@ Note : Make sure `nodeIntegration` is set to `true` under `webPreferences` while
See [provider usage documentation](https://learn.microsoft.com/graph/toolkit/providers) to learn about how to use the providers with the mgt components, to sign in/sign out, get access tokens, call Microsoft Graph, and more. See [Electron provider documentation](https://learn.microsoft.com/graph/toolkit/providers/electron).
## Usage with Context Bridge
1. Install the packages
```bash
npm install @microsoft/mgt-element @microsoft/mgt-electron-provider
```
2. Setup the context bridge in your preload script (eg. preload.ts)
```ts
import { contextBridge } from 'electron';
import { ElectronContextBridgeProvider } from '@microsoft/mgt-electron-provider/dist/Provider';
// can be named anything, like "electronApi"
contextBridge.exposeInMainWorld("main", {
electronProvider: {
mgtAuthState: (callback: (event: IpcRendererEvent, authState: string) => void) => ipcRenderer.on('mgtAuthState', callback),
token: (options?: AuthenticationProviderOptions) => ipcRenderer.invoke('token', options),
login: () => ipcRenderer.invoke('login'),
logout: () => ipcRenderer.invoke('logout'),
},
});
```
Expose the ElectronProvider methods through the context bridge. Here, we've named the api "main" for the main window, but it can be named anything. We've also made sure to put them under `electronProvider` to separate them from other methods you may add. These methods must match the `IContextBridgeImpl` interface in the `@microsoft/mgt-electron-provider` package.
3. Globally augment the `Window` interface in a declaration file (eg. preload.d.ts)
```ts
import { IContextBridgeImpl } from '@microsoft/mgt-electron-provider/dist/Provider';
export declare global {
interface Window {
// can be named anything, like "electronApi"
main: {
electronProvider: IContextBridgeImpl;
}
}
}
```
3. Initialize the provider in your renderer process (Front end, eg. renderer.ts)
```ts
import {Providers} from '@microsoft/mgt-element';
import {ElectronContextBridgeProvider} from '@microsoft/mgt-electron-provider/dist/Provider';
// initialize the auth provider globally
Providers.globalProvider = new ElectronContextBridgeProvider(window.main.electronProvider)
```
4. Initialize ElectronAuthenticator in Main.ts (Back end)
```ts
import { ElectronAuthenticator, MsalElectronConfig } from '@microsoft/mgt-electron-provider/dist/Authenticator';
...
let mainWindow = new BrowserWindow({
width: 800,
height: 800,
webPreferences: {
nodeIntegration: false // make sure this is false, we're using context bridge
}
});
let config: MsalElectronConfig = {
clientId: '<your_client_id>',
authority: '<your_authority_url>', // optional, uses common authority by default
mainWindow: mainWindow, // this is the BrowserWindow instance that requires authentication
scopes: [
'user.read',
],
};
ElectronAuthenticator.initialize(config);
```
Note : Make sure `nodeIntegration` is set to `false` under `webPreferences` while creating a new BrowserWindow instance. This is because we're using context bridge to communicate between the main and renderer processes.
See [provider usage documentation](https://learn.microsoft.com/graph/toolkit/providers) to learn about how to use the providers with the mgt components, to sign in/sign out, get access tokens, call Microsoft Graph, and more. See [Electron provider documentation](https://learn.microsoft.com/graph/toolkit/providers/electron).
### Cache Plugin
[MSAL Node](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-node) supports an in-memory cache by default and provides the ICachePlugin interface to perform cache serialization, but does not provide a default way of storing the token cache to disk. If you need persistent cache storage to enable silent log-ins or cross-platform caching, we recommend using the default implementation provided by MSAL Node [here](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/extensions/msal-node-extensions). You can import this plugin, and pass the instance of the cache plugin while initializing ElectronAuthenticator.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* -------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
* See License in the project root for license information.
* -------------------------------------------------------------------------------------------
*/

import {
GraphEndpoint,
IProvider,
MICROSOFT_GRAPH_DEFAULT_ENDPOINT,
ProviderState,
Providers,
createFromProvider
} from '@microsoft/mgt-element';
import { AuthenticationProviderOptions } from '@microsoft/microsoft-graph-client';
import type { IpcRendererEvent } from 'electron';

/**
* The interface that describes the shape of the context bridge
* that is used to communicate between the main and renderer processes.
*/
export interface IContextBridgeImpl {
mgtAuthState: (callback: (event: IpcRendererEvent, authState: string) => void) => void;
token: (options?: AuthenticationProviderOptions) => Promise<string>;
login: () => Promise<void>;
logout: () => Promise<void>;
}

/**
* ElectronProvider class to be instantiated in the a preload script.
* Responsible for communicating with ElectronAuthenticator in the main process to acquire tokens.
*
* This class uses the `contextBridge` to communicate with the main process,
* which must be passed in as a constructor parameter.
*
* @export
* @class ElectronProvider
* @extends {IProvider}
*/
export class ElectronContextBridgeProvider extends IProvider {
/**
* Name used for analytics
*
* @readonly
* @memberof IProvider
*/
public get name() {
return 'MgtElectronContextBridgeProvider';
}

private readonly contextBridge: IContextBridgeImpl;

constructor(contextBridge: IContextBridgeImpl, baseUrl: GraphEndpoint = MICROSOFT_GRAPH_DEFAULT_ENDPOINT) {
super();
this.baseURL = baseUrl;
this.contextBridge = contextBridge;
this.graph = createFromProvider(this);
this.setupProvider();
}

/**
* Sets up messaging between main and renderer to receive SignedIn/SignedOut state information
*
* @memberof ElectronProvider
*/
setupProvider() {
this.contextBridge.mgtAuthState((event, authState) => {
if (authState === 'logged_in') {
Providers.globalProvider.setState(ProviderState.SignedIn);
} else if (authState === 'logged_out') {
Providers.globalProvider.setState(ProviderState.SignedOut);
}
});
}

/**
* Gets access token (called by MGT components)
*
* @param {AuthenticationProviderOptions} [options]
* @return {*} {Promise<string>}
* @memberof ElectronProvider
*/
async getAccessToken(options?: AuthenticationProviderOptions): Promise<string> {
const token = await this.contextBridge.token(options);
return token;
}

/**
* Log in to set account information (called by mgt-login)
*
* @return {*} {Promise<void>}
* @memberof ElectronProvider
*/
async login(): Promise<void> {
Providers.globalProvider.setState(ProviderState.Loading);
await this.contextBridge.login();
}

/**
* Log out (called by mgt-login)
*
* @return {*} {Promise<void>}
* @memberof ElectronProvider
*/
async logout(): Promise<void> {
await this.contextBridge.logout();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
* -------------------------------------------------------------------------------------------
*/

export * from './ElectronContextBridgeProvider';
export * from './ElectronProvider';

0 comments on commit 8900eb4

Please sign in to comment.