I recently published in the PnP community a new Application Extension that allows the user to select a list or library and receive a real-time notification with the information that has changed. This article explains the motivation and my experience in implementing this application.
Not having to be concerned about when information that is relevant to me changes was the main motivation for creating this extension, don’t matter where it is and if it is a File, Document or a List Item. Having this information centralised in a single place was the second motivation.
This extension was developed in TypeScript, React, Fluent UI and uses Microsoft Graph API’s available to subscribe notifications, read information about changed data and save user settings.
The extension adds an icon (ringer) in the navigation bar of the sites next to the settings option, if the site is a Communication Site or next to the announcements if it is a Team Site, the Icon can be positioned in any position from the right, because the extension has a property where you can specify its position in pixels, This Icon indicates the number of notifications received, (Badge).
The extension on startup subscribes to all lists that user has selected to receive notifications and listens for changes.
Technically the extension will fetch the WebSocket EndPoint defined for the list and make a connection to that server to receive notifications.
Sample:
.
.
.
const listSubscription = (await msGraphClient
.api(`/sites/${siteId}/lists/${listId}/subscriptions/socketIo`)
.get()) as Subscription;
return listSubscription?.notificationUrl;
.
.
.
const connectToSocketIOServer = (notificationUrl: string): Socket => {
const split = notificationUrl.split("/callback?");
const socket = io(split[0], { query: split[1] as any, transports: ["websocket"] });
socket.on("connect", () => {
console.log("Connected!", notificationUrl);
});
socket.on("notification", handleNotifications);
return socket;
};
Here the link to Microsoft Graph API that explains how to use API
For the user to receive notifications he has to configure the extension and select the lists for which he wants to be notified.
Clicking on the Icon will open a Panel that allows you to view the notifications received as well as access the configuration settings identified by the gear wheel.
In the configuration settings the user can search the lists that he wants to be notified.
Here I use my React-Graph-control, ListPicker that use MicrosoftGraph Search API.
User settings are saved in the user’s OnDrive for Business in a folder called “Apps” and is known as user’s application’s personal folder. This is provide by Microsoft Graph API, Get Special folder and is automatic created when you read or write data.
Usually in Apps folder we have this organisation: /Apps/{Application Name}
, the Application name is the name of AAD registered Application, in context of SPFx has the name of SharePoint Online Client Extensibility Web Application Principal,.
It is good practice to create a subfolder for each application in order to avoid possible conflicts.
const createAppFolder = useCallback(
async (folderName: string) => {
try {
const msGraphClient = await
context.serviceScope.consume(MSGraphClientFactory.serviceKey).getClient();
if (!msGraphClient) return;
await msGraphClient.api(`/me/drive/special/approot`).header("content-
type", "application/json").put({
name: folderName,
folder: {},
});
} catch (error) {
console.log("er", error);
if (error.code !== "nameAlreadyExists") {
throw error;
}
}
},
[context.serviceScope]
);
const saveSettings = useCallback(
async (settings: string) => {
try {
const msGraphClient = await
context.serviceScope.consume(MSGraphClientFactory.serviceKey).getClient();
await msGraphClient
.api("/me/drive/special/approot:/MyListsNotifications/appsettings.json:/content")
.header("content-type", "plain/text")
.put(JSON.stringify(settings));
} catch (error) {
throw error;
}
},
[context.serviceScope]
);
const getSettings = useCallback(async (): Promise<IConfigurationListItem[]> => {
try {
const msGraphClient = await
context.serviceScope.consume(MSGraphClientFactory.serviceKey).getClient();
const downLoadUrlResponse = (await msGraphClient
.api("/me/drive/special/approot:/MyListsNotifications/appsettings.json?
select=@microsoft.graph.downloadUrl")
.get()) as HttpClientResponse;
const appSettings = await context.httpClient.get(
downLoadUrlResponse["@microsoft.graph.downloadUrl"],HttpClient.configurations.v1);
const data: IConfigurationListItem[] = JSON.parse(await appSettings.json());
return data;
} catch (error) {
throw error;
}
}, [context.serviceScope, context.httpClient]);
After connecting our lists to the corresponding SockectIO endpoint, we will receive in real time notification about the changes made to the list.
{"value":[{"subscriptionId":"9710b5f6-d97f-4794-bb65-939a4b7b0f9a","clientState":"system-managed:8082D436-D8DA-458D-96AD-34C902B73F37","expirationDateTime":"2021-07-10T20:40:15.5870000Z","resource":"b3a4755f-18be-4fbf-a6ae-e0bc14ac857e","tenantId":"ddba30c7-d040-4a20-9739-6a75edeadff2","siteUrl":"/","webId":"b06503c7-8710-4c0a-9b66-f2c36dc94a1b"}]}
With the information, I will get the most recent activity from the list with the Microsoft Graph API Recent Activities, to create a notification block with the action that was applied to the Item.
Note: Microsoft Graph API Recent Activities, is in Beta and can be changed in the future.
const getListActivities = useCallback(
async (siteId: string, listId: string):Promise<IActivity[]> => {
try {
const msGraphClient = await
serviceScope.consume(MSGraphClientFactory.serviceKey).getClient();
const listActivitiesResults = (await msGraphClient
.api(`/sites/${siteId}/lists/${listId}/activities`)
.expand("listItem($expand=fields),driveItem")
.top(1)
.version("beta")
.get()) as IActivities;
return listActivitiesResults.value;
} catch (error) {
throw error;
}
},
[serviceScope]
);
List notifications and Information of activities
I have to thank the entire Microsoft Graph team for the excellent work they have done with regard to providing API’s that make it possible to access Microsoft 365 data in a simple and versatile way.
Thank you, thank you for making a developer’s life easier.
Thank you very much for read and hope this be helpful in someway. this is available on SharePoint Framework Client-Side Extension Samples