Skip to content

Commit

Permalink
setup simple socketio connection
Browse files Browse the repository at this point in the history
  • Loading branch information
lebalz committed Jul 6, 2024
1 parent 3455eab commit 553c485
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 80 deletions.
4 changes: 3 additions & 1 deletion src/api/IoEventTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { rootStore } from '../stores/rootStore';
export enum IoEvent {
NEW_RECORD = 'NEW_RECORD',
CHANGED_RECORD = 'CHANGED_RECORD',
DELETED_RECORD = 'DELETED_RECORD'
DELETED_RECORD = 'DELETED_RECORD',
PING = 'PING'
}

export enum RecordType {
Expand Down Expand Up @@ -63,6 +64,7 @@ export type ServerToClientEvents = {
[IoEvent.NEW_RECORD]: (message: NewRecord<RecordType>) => void;
[IoEvent.CHANGED_RECORD]: (message: ChangedRecord<RecordType>) => void;
[IoEvent.DELETED_RECORD]: (message: DeletedRecord) => void;
[IoEvent.PING]: (message: { time: number }) => void;
};

export interface ClientToServerEvents {}
Expand Down
14 changes: 14 additions & 0 deletions src/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ export const setupMsalAxios = () => {
);
};

export const setupNoAuthAxios = () => {
/** clear all current interceptors and set them up... */
api.interceptors.request.clear();
api.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
config.headers['Authorization'] = JSON.stringify({ email: TEST_USERNAME });
return config;
},
(error) => {
Promise.reject(error);
}
);
};

export const checkLogin = (signal: AbortSignal) => {
return api.get('checklogin', { signal });
};
Expand Down
110 changes: 51 additions & 59 deletions src/components/HomepageFeatures/index.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,62 @@
import clsx from 'clsx';
import Heading from '@theme/Heading';
import styles from './styles.module.css';
import { useStore } from '@site/src/hooks/useStore';
import { observer } from 'mobx-react-lite';
import DefinitionList from '../DefinitionList';
import { BACKEND_URL } from '@site/src/authConfig';
import Icon from '@mdi/react';
import { mdiCheckCircle, mdiCloseCircle, mdiConnection } from '@mdi/js';
import Button from '../shared/Button';

type FeatureItem = {
title: string;
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
description: JSX.Element;
};

const FeatureList: FeatureItem[] = [
{
title: 'Easy to Use',
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
description: (
<>
Docusaurus was designed from the ground up to be easily installed and used to get your website
up and running quickly.
</>
)
},
{
title: 'Focus on What Matters',
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
description: (
<>
Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go ahead and move your
docs into the <code>docs</code> directory.
</>
)
},
{
title: 'Powered by React',
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
description: (
<>
Extend or customize your website layout by reusing React. Docusaurus can be extended while
reusing the same header and footer.
</>
)
}
];

function Feature({ title, Svg, description }: FeatureItem) {
return (
<div className={clsx('col col--4')}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<Heading as="h3">{title}</Heading>
<p>{description}</p>
</div>
</div>
);
}

export default function HomepageFeatures(): JSX.Element {
const HomepageFeatures = observer(() => {
const socketStore = useStore('socketStore');
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
<h2>Socket.IO</h2>
<DefinitionList>
<dt>URL</dt>
<dd>{BACKEND_URL}</dd>
<dt>Connected?</dt>
<dd>
{socketStore.isLive
? (<span><Icon path={mdiCheckCircle} size={0.8} color='var(--ifm-color-success)' />{' '}Live</span>)
: (<span><Icon path={mdiCloseCircle} size={0.8} color='var(--ifm-color-danger)' />{' '}Offline</span>)
}
</dd>
<dt>Ping-Events</dt>
<dd>For Demo-Purpose: The API pings every second</dd>
<dd>{socketStore.messages.length}{' Messages'}</dd>
<dd>{(socketStore.messages.slice(-1)?.[0]?.time - socketStore.messages[0]?.time) / 1000}{' s Live'}</dd>
<dd>{socketStore.messages.slice(-1)?.[0]?.time}{' Timestamp of latest message'}</dd>
<dt>Connection</dt>
<dd>
<Button
icon={mdiConnection}
text="Connect"
onClick={() => {
socketStore.resetUserData();
socketStore.connect();
}}
disabled={socketStore.isLive}
color='blue'
/>
</dd>
<dd>
<Button
icon={mdiCloseCircle}
text="Disconnect"
onClick={() => socketStore.disconnect()}
disabled={!socketStore.isLive}
color='red'
/>
</dd>
</DefinitionList>
</div>
</section>
);
}
});

export default HomepageFeatures;
3 changes: 3 additions & 0 deletions src/css/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
--ifm-color-blue: #3578e5;
--ifm-color-blue-dark: #306cce;
--ifm-color-blue-darker: #2d66c3;
}

/* For readability concerns, you should choose a lighter palette in dark mode. */
Expand Down
23 changes: 10 additions & 13 deletions src/stores/SessionStore.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AccountInfo, IPublicClientApplication } from '@azure/msal-browser';
import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { action, computed, flow, flowResult, observable, reaction } from 'mobx';
import { RootStore } from './rootStore';
import { Role, User, logout } from '../api/user';
import { Role, User, currentUser, logout } from '../api/user';
import Storage, { PersistedData, StorageKey } from './utils/Storage';
import siteConfig from '@generated/docusaurus.config';
import iStore from './iStore';
const { NO_AUTH, TEST_USERNAME } = siteConfig.customFields as { TEST_USERNAME?: string; NO_AUTH?: boolean };

class State {
Expand All @@ -14,11 +15,11 @@ class State {
constructor() {}
}

export class SessionStore {
private readonly root: RootStore;
export class SessionStore extends iStore {
readonly root: RootStore;
private static readonly NAME = 'SessionStore' as const;

private stateRef: { state: State } = observable({ state: new State() }, { state: observable.ref });
@observable private accessor stateRef: State = new State();

@observable accessor authMethod: 'apiKey' | 'msal';

Expand All @@ -29,6 +30,7 @@ export class SessionStore {
@observable accessor storageSyncInitialized = false;

constructor(store: RootStore) {
super();
this.root = store;
const data = Storage.get<PersistedData>(StorageKey.SessionStore) || {};
this.rehydrate(data);
Expand Down Expand Up @@ -79,22 +81,17 @@ export class SessionStore {

@computed
get account(): AccountInfo | null | undefined {
return this.stateRef.state.account;
return this.stateRef.account;
}

@action
setAccount(account?: AccountInfo | null) {
this.stateRef.state.account = account;
}

@computed
get isStudent(): boolean {
return this.account?.username?.includes('@edu.') ?? false;
this.stateRef.account = account;
}

@computed
get isLoggedIn(): boolean {
return this.authMethod === 'apiKey' ? !!this.currentUserId : !!this.stateRef.state.account;
return this.authMethod === 'apiKey' ? !!this.currentUserId : !!this.stateRef.account;
}

@action
Expand Down
12 changes: 8 additions & 4 deletions src/stores/SocketDataStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ import {
} from '../api/IoEventTypes';
import { BACKEND_URL } from '../authConfig';
interface Message {
type: string;
message: string;
time: number;
}

export class SocketDataStore {
Expand All @@ -45,7 +44,6 @@ export class SocketDataStore {
return Promise.reject(error);
}
);
console.log('SocketDataStore: Connect Socket.IO to', BACKEND_URL);
reaction(
() => this.isLive,
action((isLive) => {
Expand Down Expand Up @@ -112,7 +110,7 @@ export class SocketDataStore {
}
this.socket.on('connect', () => {
/**
* maybe there is a newer version to add headers,
* maybe there is a newer version to add headers?
* @see https://socket.io/docs/v4/client-options/#extraheaders
*/
api.defaults.headers.common['x-metadata-socketid'] = this.socket.id;
Expand All @@ -135,6 +133,12 @@ export class SocketDataStore {
this.socket.on(IoEvent.NEW_RECORD, this.createRecord.bind(this));
this.socket.on(IoEvent.CHANGED_RECORD, this.updateRecord.bind(this));
this.socket.on(IoEvent.DELETED_RECORD, this.deleteRecord.bind(this));
this.socket.on(IoEvent.PING, this.onPing.bind(this));
}

@action
onPing({ time }) {
this.messages.push({ time });
}

@action
Expand Down
14 changes: 12 additions & 2 deletions src/stores/UserStore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { User as UserProps, find as apiFind } from '../api/user';
import { User as UserProps, find as apiFind, currentUser } from '../api/user';
import { RootStore } from './rootStore';
import User from '../models/User';
import _ from 'lodash';
Expand All @@ -21,7 +21,7 @@ export class UserStore extends iStore {
setTimeout(() => {
// attempt to load the previous state of this store from localstorage
this.rehydrate();
}, 1);
}, 0);
}

@action
Expand Down Expand Up @@ -98,4 +98,14 @@ export class UserStore extends iStore {
});
});
}

@action
loadCurrent() {
const res = this.withAbortController('load-user', async (signal) => {
return currentUser(signal.signal).then((res) => {
return this.addToStore(res.data);
});
});
return res;
}
}
18 changes: 18 additions & 0 deletions src/stores/rootStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DocumentRootStore } from './DocumentRootStore';
import { UserStore } from './UserStore';
import { SessionStore } from './SessionStore';
import { SocketDataStore } from './SocketDataStore';
import { action, reaction } from 'mobx';

export class RootStore {
documentRootStore: DocumentRootStore;
Expand All @@ -15,7 +16,24 @@ export class RootStore {
this.sessionStore = new SessionStore(this);
this.userStore = new UserStore(this);
this.socketStore = new SocketDataStore(this);
reaction(
() => this.sessionStore.isLoggedIn,
(isLoggedIn) => {
if (isLoggedIn) {
this.userStore.loadCurrent().then((user) => {
if (user) {
this.socketStore.reconnect();
}
});
}
}
)
}

// @action
// load() {
// this.userStore.loadCurrent();
// }
}

export const rootStore = Object.freeze(new RootStore());
Expand Down
8 changes: 7 additions & 1 deletion src/theme/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import Head from '@docusaurus/Head';
import siteConfig from '@generated/docusaurus.config';
import { useLocation } from '@docusaurus/router';
import { AccountInfo, EventType, InteractionStatus, PublicClientApplication } from '@azure/msal-browser';
import { setupMsalAxios, setupDefaultAxios } from '../api/base';
import { setupMsalAxios, default as axiosAPI, setupNoAuthAxios } from '../api/base';
import { useStore } from '../hooks/useStore';
import { runInAction } from 'mobx';
import { usePluginData } from '@docusaurus/useGlobalData';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import { DocumentRootStore } from '../stores/DocumentRootStore';
import { InternalAxiosRequestConfig } from 'axios';
const { NO_AUTH, TEST_USERNAME } = siteConfig.customFields as { TEST_USERNAME?: string; NO_AUTH?: boolean };
export const msalInstance = new PublicClientApplication(msalConfig);

Expand Down Expand Up @@ -43,6 +44,11 @@ if (NO_AUTH) {

const MsalWrapper = observer(({ children }: { children: React.ReactNode }) => {
const sessionStore = useStore('sessionStore');
React.useEffect(() => {
if (NO_AUTH && process.env.NODE_ENV !== 'production' && TEST_USERNAME) {
setupNoAuthAxios();
}
}, []);
React.useEffect(() => {
/**
* DEV MODE
Expand Down

0 comments on commit 553c485

Please sign in to comment.