+
+```jsx
+import React, { useEffect, useState, useMemo } from 'react';
+import ReactDOM from 'react-dom/client';
+import { TopicClient, CredentialProvider } from '@gomomento/sdk-web';
+
+const HEARTBEAT_INTERVAL_MS = 5000;
+
+function getMediaIdFromQuery() {
+ const params = new URLSearchParams(window.location.search);
+ return params.get('id');
+}
+
+function Device() {
+ const [topicClient, setTopicClient] = useState(null);
+
+ const mediaId = useMemo(() => getMediaIdFromQuery(), []);
+ const deviceId = useMemo(() => {
+ const savedDeviceId = localStorage.getItem('deviceId');
+ if (savedDeviceId) return savedDeviceId;
+
+ const newDeviceId = crypto.randomUUID();
+ localStorage.setItem('deviceId', newDeviceId);
+ return newDeviceId;
+ }, []);
+
+ const message = useMemo(() => JSON.stringify({ deviceId, mediaId }), [deviceId, mediaId]);
+
+ useEffect(() => {
+ async function initTopicClient() {
+ const response = await fetch('http://localhost:3000/tokens', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ accountId: 'account-id' }),
+ });
+ if (response.ok) {
+ const { token } = await response.json();
+ const topicClient = new TopicClient({
+ credentialProvider: CredentialProvider.fromString(token),
+ });
+ setTopicClient(topicClient);
+ }
+ }
+
+ initTopicClient();
+ }, [mediaId]);
+
+ useEffect(() => {
+ if (topicClient) {
+ const intervalId = setInterval(() => {
+ topicClient.publish('video', 'heartbeat', message);
+ }, HEARTBEAT_INTERVAL_MS);
+
+ return () => clearInterval(intervalId);
+ }
+ }, [topicClient, mediaId, message]);
+
+ return (
+
+
Device {deviceId}: {topicClient ? 'Connected' : 'Not Connected'}
+
+ );
+}
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render();
+
+```
+
+
+