#OpenToWork

Available for new opportunities! Let's build something amazing together.

Get in touch

RealTime News Updates in SharePoint Online with SPFx

post

Recently, I discovered the Socket.IO endpoint in Microsoft Graph, which has enabled me to implement real-time functionality in SharePoint Online. This feature allows SPFx solutions and other types of applications to receive near-instantaneous change notifications for drives and lists, facilitating dynamic updates without manual refreshes.

alert

It’s important to note that utilizing this approach requires establishing a separate WebSocket connection for each library or list you wish to monitor. This can lead to multiple concurrent connections, which may impact performance and resource utilization.

Integrating real-time updates into SharePoint Online solutions enhances user engagement by ensuring content is always up to date. The approach utilizes SharePoint REST API endpoints to establish a WebSocket connection via Socket.IO, enabling dynamic updates without relying on Microsoft Graph (that way you do not have to request permissions).

note

If you want, you can change the code in the upcoming code snippets to make use of the Microsoft Graph

Implementing real-time news updates in SharePoint online

The provided code sample demonstrates how to create a React functional component within the SharePoint Framework (SPFx) that listens for changes in a SharePoint site pages library and updates the UI accordingly. Here’s a breakdown of the implementation:

Fetching pages

The fetchPages function retrieves the latest pages from the SharePoint site’s sitepages library using the SPHttpClient.

Fetching SharePoint Pages
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const fetchPages = React.useCallback(async () => {
  const apiUrl = `${context.pageContext.web.absoluteUrl}/_api/sitepages/pages?$select=Title,Description,AbsoluteUrl&$top=5&$orderby=Modified desc`;
  const response = await context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);

  if (!response.ok) {
    return;
  }

  const data = await response.json();
  setPages(data.value);
}, [context]);

Obtaining the notification URL (websocket)

The fetchNotificationUrl function constructs the API URL to retrieve the notificationUrl for the specific list. This URL is essential for establishing a WebSocket connection.

Fetch the notification URL from SharePoint
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const fetchNotificationUrl = React.useCallback(async () => {
  const listId = context.pageContext.list?.id;
  if (!listId) {
    return;
  }

  const apiUrl = `${context.pageContext.web.absoluteUrl}/_api/v2.0/sites('${location.host},${context.pageContext.site.id},${context.pageContext.web.id}')/lists('${listId}')/drive/root/subscriptions/socketIo`;

  const response = await context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);

  if (!response.ok) {
    return;
  }

  const data = await response.json();
  setNotificationUrl(data.notificationUrl);
}, [context]);
info

I derived this endpoint from the Microsoft Graph - websocket endpoint.

The Socket.IO endpoint provides you with a URL that you can use to establish a WebSocket connection. This connection will allow you to receive real-time updates for the specific list or library you are monitoring.

Establishing a WebSocket connection

Upon obtaining the notificationUrl, a WebSocket connection is established using Socket.IO. The component listens for ’notification’ events and updates the list of pages when changes occur.

First install the socket.io-client package:

1
npm install socket.io-client

Then, you can establish the WebSocket connection as shown below:

Establishing a WebSocket connection
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
React.useEffect(() => {
  if (!notificationUrl) {
    return;
  }

  // Make sure to define the `transports` option to only use the WebSocket transport
  // It will fail if not defined, or when using polling
  const socket = io(notificationUrl, { transports: ['websocket'] });

  socket.on('connect', () => {
    console.log('Connected to the notification service');
    setListening(true);
  });

  socket.on('notification', (data) => {
    console.log('Notification received:', data);
    fetchPages();
  });

  return () => {
    socket.disconnect();
  };
}, [notificationUrl]);

Initializing the data fetching

The useEffect hook ensures that the component fetches the initial set of pages and the notification URL when it mounts.

Initializing the data fetching
1
2
3
4
React.useEffect(() => {
  fetchPages();
  fetchNotificationUrl();
}, []);

Rendering the UI

Finally, the component renders the list of pages and displays a loading indicator while fetching data.

Rendering the UI
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div>
  <h3>Real-Time News</h3>
  <p>Status: {listening ? 'Listening for changes...' : 'Not connected'}</p>
  <ul>
    {pages.map((page, index) => (
      <li key={index}>
        <strong>{page.Title}</strong>: {page.Description} - <a href={page.AbsoluteUrl} target="_blank" rel="noopener noreferrer">Read more</a>
      </li>
    ))}
  </ul>
</div>

The outcome of this implementation looks like this:

Real-time updates with websockets

The complete component

Here is the complete React functional component that implements real-time news updates in SharePoint Online using SPFx:

Real-time news component
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import { WebPartContext } from '@microsoft/sp-webpart-base';
import * as React from 'react';
import { io } from 'socket.io-client';
import { SPHttpClient } from '@microsoft/sp-http';

export interface IRealTimeNewsProps {
  context: WebPartContext;
}

export const RealTimeNews: React.FunctionComponent<IRealTimeNewsProps> = ({
  context
}: React.PropsWithChildren<IRealTimeNewsProps>) => {
  const [listening, setListening] = React.useState(false);
  const [pages, setPages] = React.useState<{ Title: string, Description: [], AbsoluteUrl: string }[]>([]);
  const [notificationUrl, setNotificationUrl] = React.useState<string | null>(null);

  const fetchPages = React.useCallback(async () => {
    const apiUrl = `${context.pageContext.web.absoluteUrl}/_api/sitepages/pages?$select=Title,Description,AbsoluteUrl&$top=5&$orderby=Modified desc`;
    const response = await context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);

    if (!response.ok) {
      return;
    }

    const data = await response.json();
    setPages(data.value);
  }, [context]);

  const fetchNotificationUrl = React.useCallback(async () => {
    const listId = context.pageContext.list?.id;
    if (!listId) {
      return;
    }

    const apiUrl = `${context.pageContext.web.absoluteUrl}/_api/v2.0/sites('${location.host},${context.pageContext.site.id},${context.pageContext.web.id}')/lists('${listId}')/drive/root/subscriptions/socketIo`;

    const response = await context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);

    if (!response.ok) {
      return;
    }

    const data = await response.json();
    setNotificationUrl(data.notificationUrl);
  }, [context]);

  React.useEffect(() => {
    if (!notificationUrl) {
      return;
    }

    const socket = io(notificationUrl, { transports: ['websocket'] });

    socket.on('connect', () => {
      console.log('Connected to the notification service');
      setListening(true);
    });

    socket.on('notification', (data) => {
      console.log('Notification received:', data);
      // eslint-disable-next-line @typescript-eslint/no-floating-promises
      fetchPages();
    });
  }, [notificationUrl]);

  React.useEffect(() => {
    // eslint-disable-next-line @typescript-eslint/no-floating-promises
    fetchPages();
    // eslint-disable-next-line @typescript-eslint/no-floating-promises
    fetchNotificationUrl();
  }, [notificationUrl]);

  return (
    <div>
      <h3>Real-Time News</h3>
      <p>Status: {listening ? 'Listening for changes...' : 'Not connected'}</p>
      <ul>
        {pages.map((page, index) => (
          <li key={index}>
            <strong>{page.Title}</strong>: {page.Description} - <a href={page.AbsoluteUrl} target="_blank" rel="noopener noreferrer">Read more</a>
          </li>
        ))}
      </ul>
    </div>
  );
};

Alternative approach using Microsoft Graph

While your current implementation avoids the need for additional permissions by utilizing SharePoint REST API endpoints, it’s worth noting that similar functionality can be achieved through Microsoft Graph. However, this approach requires specific permissions to access the necessary resources.

To use Microsoft Graph for real-time notifications, you would need to request permissions such as Sites.Read.All and Files.Read. These permissions must be declared in your solution’s package-solution.json file under the webApiPermissionRequests property:

package-solution.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
  "solution": {
    "webApiPermissionRequests": [
      {
        "resource": "Microsoft Graph",
        "scope": "Sites.Read.All"
      },
      {
        "resource": "Microsoft Graph",
        "scope": "Files.Read"
      }
    ]
  }
}

After deploying your solution, an administrator must approve these permissions in the SharePoint admin center. This process is detailed in the Microsoft documentation.

Conclusion

By leveraging SharePoint REST API endpoints and Socket.IO, you can implement real-time updates in your SharePoint Online environment without the need for additional permissions. This approach simplifies the deployment process and maintains dynamic content for users. Alternatively, integrating with Microsoft Graph offers a robust solution but requires appropriate permissions and administrative consent.

Comments

Back to top