Hi, Can this project be used in ElectronJS project? If yes, how to auth?
thanks
Peter
Hi @peterremote1980,
I don't know to much about ElectronJS sepcifically, but I guess you could use application settings and auth via Node.js.
Here is an example: https://pnp.github.io/pnpjs/documentation/getting-started/#connect-to-sharepoint-from-node
Yes.
As for auth you have a few choices. You can use the examples from @simonagren's link or use any of the other available auth options such as the code auth flow. Here is a class I have used from another project to handle this flow. This allows each person to auth as themselves and get as well the delegated permissions from the app. Also easier security as you don't have to necessarily track an app id and secret within the app. Once you have a token with the right permissions you can call graph, sharepoing, etc as needed and as the user.
import { AuthenticationContext, TokenResponse, ErrorResponse, UserCodeInfo } from "adal-node";
import { IServiceContainer } from "../../types";
import { Service } from "./service";
import { objectDefinedNotNull } from "@pnp/common";
export interface AuthTokenData {
resource: string;
expiresOn: string | Date;
token: string;
refreshToken: string;
}
export class CLIAADTokenService extends Service {
public name = "CLI AAD Token Service";
public container: IServiceContainer = null;
public shouldPersist = false;
public constructor(
private appId: string,
loginAuthority = "https://login.microsoftonline.com/common",
private authContext = new AuthenticationContext(loginAuthority)) {
super();
}
public async getToken(resource: string): Promise<string> {
const resourceKey = this.normalizeResourceKey(resource);
// const persistentFileName = `c336a1f1_${this.appId}_${resourceKey}`;
return new Promise<string>(async (resolve, reject) => {
let authData: AuthTokenData | null = null;
let state = await this.getServiceState<Map<string, AuthTokenData>>();
if (!objectDefinedNotNull(state)) {
state = new Map<string, AuthTokenData>();
}
if (state.has(resourceKey)) {
authData = state.get(resourceKey);
const expiresOn: Date = new Date(authData.expiresOn);
if (expiresOn > new Date()) {
resolve(authData.token);
} else {
// remove this from the cache as it is expired
state.delete(resourceKey);
await this.setServiceState(state);
this.authContext.acquireTokenWithRefreshToken(
authData.refreshToken as string,
this.appId, authData.resource,
async (e: Error, r: TokenResponse | ErrorResponse): Promise<void> => {
if (e) {
reject((r && (r as any).error_description) || e.message);
} else {
const r2 = await this.setCacheFromResponse(resource, r as TokenResponse);
resolve(r2.token);
}
});
}
} else {
const refreshToken = state.size > 0 ? state.entries().next().value[1] : null;
if (refreshToken === null) {
this.authContext.acquireUserCode(resource, this.appId, "en-us", (error: Error, response: UserCodeInfo) => {
if (error) {
return reject(error);
}
console.log(response.message);
this.authContext.acquireTokenWithDeviceCode(resource, this.appId, response, async (error2: Error, r: TokenResponse | ErrorResponse) => {
if (error2) {
return reject(error2);
}
const r2 = await this.setCacheFromResponse(resource, r as TokenResponse);
resolve(r2.token);
});
});
} else {
this.authContext.acquireTokenWithRefreshToken(
refreshToken.refreshToken,
this.appId,
resource,
async (e: Error, r: TokenResponse | ErrorResponse): Promise<void> => {
if (e) {
reject((r && (r as any).error_description) || e.message);
} else {
const r2 = await this.setCacheFromResponse(resource, r as TokenResponse);
resolve(r2.token);
}
});
}
}
});
}
private normalizeResourceKey(resource: string): string {
return resource.toLowerCase();
}
private async setCacheFromResponse(resource: string, tokenResponse: TokenResponse): Promise<AuthTokenData> {
const key = this.normalizeResourceKey(resource);
const authData = {
expiresOn: tokenResponse.expiresOn,
refreshToken: tokenResponse.refreshToken || "",
resource,
token: tokenResponse.accessToken,
};
// read current state
let state = await this.getServiceState<Map<string, AuthTokenData>>();
if (!objectDefinedNotNull(state)) {
state = new Map<string, AuthTokenData>();
}
// update
state.set(key, authData);
// update our state in the store
this.setServiceState(state);
return authData;
}
}
Thank you all, the main problem is that in electron, it is javascript not typescript. So i just do 'import { sp } from "@pnp/sp";' will throw compile error. Is that mean the PNP basic requirement is typescript?
Thanks
/Users/peter/Desktop/electron-quick-start>npm start
> [email protected] start /Users/peter/Desktop/electron-quick-start
> electron .
App threw an error during load
/Users/peter/Desktop/electron-quick-start/main.js:1
(function (exports, require, module, __filename, __dirname, process, global, Buffer) { return
function (exports, require, module, __filename, __dirname) { import { sp } from "@pnp/sp";
^
SyntaxError: Unexpected token {
at new Script (vm.js:79:7)
at createScript (vm.js:251:10)
at Object.runInThisContext (vm.js:303:10)
at Module._compile (internal/modules/cjs/loader.js:660:28)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:704:10)
at Module.load (internal/modules/cjs/loader.js:602:32)
at tryModuleLoad (internal/modules/cjs/loader.js:541:12)
at Function.Module._load (internal/modules/cjs/loader.js:533:3)
at loadApplicationPackage (/Users/peter/Desktop/electron-quick-start/node_modules/electron
/dist/Electron.app/Contents/Resources/default_app.asar/main.js:119:12)
at Object.<anonymous> (/Users/peter/Desktop/electron-quick-start/node_modules/electron/dis
t/Electron.app/Contents/Resources/default_app.asar/main.js:164:5)
May be i think so much, i should use typescript in electronjs
You could "import" the package like this:
const sp = require("@pnp/sp").sp;
Please help
const {sp} = require('@pnp/sp');
const {SPFetchClient} =require("@pnp/nodejs");
sp.setup({
sp: {
fetchClientFactory: () => {
console.log(1);
return new SPFetchClient("https://quantr.sharepoint.com", "d9bbaasdasdd85-d4e4-4e8e-9d06-ee28ffde4d79", "guvUHN943@_*{lpayasdasdCZLR81");
},
},
});
I got:
(node:16061) UnhandledPromiseRejectionWarning: Error: Error making HttpClient request in query
able [403] Forbidden ::>
@peterremote1980,
Did you grant your add-in a certain level of permissions? If you dealing only on a sitecollection level this description also might be helpful. E.g. you probably need something like:
<AppPermissionRequests AllowAppOnlyPolicy="true">
<AppPermissionRequest Scope="http://sharepoint/content/sitecollection" Right="FullControl" />
</AppPermissionRequests>
Going to close this issue as the original question is answered with sufficient samples provided. We have a docs page on setting up your application for testing which can help with registering your application. There are also many additional resources available on the web for registering your add-in and understanding SharePoint.
You can use the Microsoft Tech Community or StackOverflow for questions not related specifically to this library.
Thanks!
No, the docs page doesn't work
Most helpful comment
Yes.
As for auth you have a few choices. You can use the examples from @simonagren's link or use any of the other available auth options such as the code auth flow. Here is a class I have used from another project to handle this flow. This allows each person to auth as themselves and get as well the delegated permissions from the app. Also easier security as you don't have to necessarily track an app id and secret within the app. Once you have a token with the right permissions you can call graph, sharepoing, etc as needed and as the user.